# frozen_string_literal: true module Backup module Database class MongoDB < Base class Error < Backup::Error; end ## # Name of the database that needs to get dumped attr_accessor :name ## # Credentials for the specified database attr_accessor :username, :password, :authdb ## # Connectivity options attr_accessor :host, :port ## # IPv6 support (disabled by default) attr_accessor :ipv6 ## # Collections to dump, collections that aren't specified won't get dumped attr_accessor :only_collections ## # Additional "mongodump" options attr_accessor :additional_options ## # Forces mongod to flush all pending write operations to the disk and # locks the entire mongod instance to prevent additional writes until the # dump is complete. # # Note that if Profiling is enabled, this will disable it and will not # re-enable it after the dump is complete. attr_accessor :lock ## # Creates a dump of the database that includes an oplog, to create a # point-in-time snapshot of the state of a mongod instance. # # If this option is used, you would not use the `lock` option. # # This will only work against nodes that maintain a oplog. # This includes all members of a replica set, as well as master nodes in # master/slave replication deployments. attr_accessor :oplog def initialize(model, database_id = nil, &block) super instance_eval(&block) if block_given? end def perform! super lock_database if @lock dump! package! ensure unlock_database if @lock end private ## # Performs all required mongodump commands, dumping the output files # into the +dump_packaging_path+ directory for packaging. def dump! FileUtils.mkdir_p dump_packaging_path collections = Array(only_collections) if collections.empty? run(mongodump) else collections.each do |collection| run("#{mongodump} --collection='#{collection}'") end end end ## # Creates a tar archive of the +dump_packaging_path+ directory # and stores it in the +dump_path+ using +dump_filename+. # # /databases/MongoDB[-].tar[.gz] # # If successful, +dump_packaging_path+ is removed. def package! pipeline = Pipeline.new dump_ext = "tar".dup pipeline << "#{utility(:tar)} -cf - " \ "-C '#{dump_path}' '#{dump_filename}'" if model.compressor model.compressor.compress_with do |command, ext| pipeline << command dump_ext << ext end end pipeline << "#{utility(:cat)} > " \ "'#{File.join(dump_path, dump_filename)}.#{dump_ext}'" pipeline.run if pipeline.success? FileUtils.rm_rf dump_packaging_path log!(:finished) else raise Error, "Dump Failed!\n#{pipeline.error_messages}" end end def dump_packaging_path File.join(dump_path, dump_filename) end def mongodump "#{utility(:mongodump)} #{name_option} #{credential_options} " \ "#{connectivity_options} #{ipv6_option} #{oplog_option} " \ "#{user_options} --out='#{dump_packaging_path}'" end def name_option return unless name "--db='#{name}'" end def credential_options opts = [] opts << "--username='#{username}'" if username opts << "--password='#{password}'" if password opts << "--authenticationDatabase='#{authdb}'" if authdb opts.join(" ") end def connectivity_options opts = [] opts << "--host='#{host}'" if host opts << "--port='#{port}'" if port opts.join(" ") end def ipv6_option "--ipv6" if ipv6 end def oplog_option "--oplog" if oplog end def user_options Array(additional_options).join(" ") end def lock_database lock_command = <<-EOS.gsub(%r{^ +}, "") echo 'use admin db.setProfilingLevel(0) db.fsyncLock()' | #{mongo_shell} EOS run(lock_command) end def unlock_database unlock_command = <<-EOS.gsub(%r{^ +}, "") echo 'use admin db.fsyncUnlock()' | #{mongo_shell} EOS run(unlock_command) end def mongo_shell cmd = "#{utility(:mongo)} #{connectivity_options}".rstrip cmd << " #{credential_options}".rstrip cmd << " #{ipv6_option}".rstrip cmd << " '#{name}'" if name cmd end end end end