Mongodump and upload to S3 using knife

Quite recently I have had to write a knife plugin to take mongo dumps. This post contains the code for the same with a few assumptions in place.

Assumptions:

1.  CentOS 6.3, mongo db version 2.4.1
2.  Chef environment has the db config as below :
     override_attributes({ database:  { user: 'db_user',
                 dump_dir: '/opt/mongodb-dump',
                 auth: true,
                 replica: {
                        id: 'rsTest',
                        mongo_primary:'x.x.x.x:27017',
                        members: ['x.x.x.x:27017'],
                        arbiter: 'x.x.x.x:27017'}
                  },

      })
3.  The database credentials are written in encrypted data bag.
4.  The data bag key is placed in /etc/chef/encrypted_data_bag_secret. 
5.  The data bag is called db_secret_data and each item has the same name as the environments in chef.
6.  Each item looks as below:
      {
         "id": "env_showcase",
         "database": "<encoded_secret>"
      }

7.  The data base has a hash encoded which when decoded reads
      { 
         "user": "db_user",
         "passwd": "password"
      }


KNIFE PLUGIN

module KnifePlugins
  class MongoDump < Chef::Knife
    banner 'knife mongo dump'
   
    DBS = [:admin, :test, :development]
   
    option :db_env,
      :long => '--db-env DB_ENV',
      :description => 'Environment to take the dump from'

    deps do
      require 'chef/search/query'
    end
    

    def run
      @env = config[:db_env]
      @s3_bucket = ENV['S3_BUCKET']

      if !valid_env(@env)
        ui.fatal 'Please provide environment name' +' e.g. knife mongo dump --db-env ft'
        exit 1
      end

      db_nodes = db_node_search(@env)
      if db_nodes.nil?
        ui.msg "No db server nodes found for environment #{@env}"
        exit 1
      end

     db_nodes.each do |db_node|
        if db_node.include?("ec2")
          node_id = db_node.ec2.local_hostname
        else
          node_id = db_node.ipaddress
        end
       
        if node_id == db_node.database.replica.mongo_primary.split(":")[0]
          @db_primary = node_id
          @db_port = db_node.database.replica.mongo_primary.split(":")[1]
        end
      end


      ui.msg '-'*80
      ui.msg cmd=dbdump_command(@db_primary, @db_port, @env)
      %x[#{cmd}] 

    end

    private

    def db_node_search env
      query = "chef_environment:#{env} AND roles:db_server"
      query_nodes = Chef::Search::Query.new
      db_servers = query_nodes.search('node', query)
      return db_servers[0] if db_servers[0].size > 0
    end

    def valid_env env
      true if !env.nil? && !env.strip.empty?
    end

    def dbdump_command db_host, db_port, env
      dump_dir = "/opt/mongo_backup/#{env}-mongodump-$timestamp"

      db_creds = load_databag(env)
      cmd = "set -e; timestamp=$(date +'%Y%m%d%H%M');"

      DBS.collect do |db|
        cmd += "mongodump --host #{db_host} --port #{db_port} -u #{db_creds['database']['user']} -p#{db_creds['database']['passwd']} -d #{db} --out #{dump_dir};"
    end.join(';')

      db_dump_cmd_str = "rm -f /tmp/db_dump.sh;"

      if !@s3_bucket.nil? && !@s3_bucket.strip.empty?
        cmd += "s3cmd sync --force #{dump_dir} #{@s3_bucket}"
      end
      db_dump_cmd_str += "echo '#{cmd}' > /tmp/db_dump.sh;"
      db_dump_cmd_str += 'sh /tmp/db_dump.sh;'
    end

    def load_databag env
      db_secret = Chef::EncryptedDataBagItem.load_secret('/etc/chef/encrypted_data_bag_secret')
      db_creds = Chef::EncryptedDataBagItem.load('db_secret_data', env, db_secret)
      db_creds
    end

  end
end

Comments

Popular posts from this blog

To DR or Not To DR

Load Balancer with SSL offloading - nginx + HAProxy

High Availability NAT for AWS VPC with Multiple Private Subnets.