From 56c0014aee8356caec60596f32bf03776f548fbe Mon Sep 17 00:00:00 2001 From: Gavin Williams Date: Tue, 2 Jun 2015 14:18:23 +0100 Subject: [PATCH] (MODULES-1947) Improve support for MongoDB authentication and replicaset management. Adds the ability to create an 'administration' MongoDB user account, which then gets stored in a mongorc.js file which enables Puppet to connect to MongoDB without credentials. Admin username and password can be over-ridden via 'admin_username' and 'admin_password' parameters. Replica set configuration can be completed as part of mongodb::server class by either providing a list of members using 'replset_members', or a full replica set config hash using 'replset_config'. Alternatively, mongodb::replset can be used to configure replicaset seperately. Any attempt to manage mongodb_db or mongodb_user resources on non-master replicaset members will generate a warning instead of failing. --- .gitignore | 2 + README.md | 36 ++++ lib/puppet/provider/mongodb.rb | 55 +++++- .../provider/mongodb_database/mongodb.rb | 12 +- lib/puppet/provider/mongodb_replset/mongo.rb | 103 ++++++----- lib/puppet/provider/mongodb_user/mongodb.rb | 160 ++++++++++-------- lib/puppet/type/mongodb_replset.rb | 5 + manifests/db.pp | 5 +- manifests/params.pp | 4 + manifests/replset.pp | 5 + manifests/server.pp | 70 ++++++++ manifests/server/config.pp | 19 +++ spec/acceptance/replset_spec.rb | 137 ++++++++++++++- spec/acceptance/server_spec.rb | 113 +++++++++++++ spec/classes/server_config_spec.rb | 52 ++++-- spec/classes/server_spec.rb | 105 +++++++++++- spec/defines/db_spec.rb | 3 +- .../provider/mongodb_database/mongodb_spec.rb | 5 + .../provider/mongodb_replset/mongodb_spec.rb | 29 +++- .../provider/mongodb_user/mongodb_spec.rb | 17 +- templates/mongorc.js.erb | 27 +++ 21 files changed, 812 insertions(+), 152 deletions(-) create mode 100644 templates/mongorc.js.erb diff --git a/.gitignore b/.gitignore index b5db85e05..ac231ef02 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ spec/fixtures/ coverage/ .idea/ *.iml +.ruby-* +log/ diff --git a/README.md b/README.md index 2bea5d4f9..44fc3ee4a 100644 --- a/README.md +++ b/README.md @@ -398,6 +398,22 @@ Use this setting to enable shard server mode for mongod. Use this setting to configure replication with replica sets. Specify a replica set name as an argument to this set. All hosts must have the same set name. +#####`replset_members` +An array of member hosts for the replica set. +Mutually exclusive with `replset_config` param. + +#####`replset_config` +A hash that is used to configure the replica set. +Mutually exclusive with `replset_members` param. + +```puppet +class mongodb::server { + replset => 'rsmain', + replset_config => { 'rsmain' => { ensure => present, members => ['host1:27017', 'host2:27017', 'host3:27017'] } } + +} +``` + #####`rest` Set to true to enable a simple REST interface. Default: false @@ -473,6 +489,23 @@ You should not set this for MongoDB versions < 3.x #####`restart` Specifies whether the service should be restarted on config changes. Default: 'true' +#####`create_admin` +Allows to create admin user for admin database. +Redefine these parameters if needed: + +#####`admin_username` +Administrator user name + +#####`admin_password` +Administrator user password + +#####`admin_roles` +Administrator user roles + +#####`store_creds` +Store admin credentials in mongorc.js file. Uses with `create_admin` parameter + + ####Class: mongodb::mongos class. This class should only be used if you want to implement sharding within your mongodb deployment. @@ -565,6 +598,9 @@ The maximum amount of two second tries to wait MongoDB startup. Default: 10 #### Provider: mongodb_user 'mongodb_user' can be used to create and manage users within MongoDB database. +*Note:* if replica set is enabled, replica initialization has to come before +any user operations. + ```puppet mongodb_user { testuser: name => 'testuser', diff --git a/lib/puppet/provider/mongodb.rb b/lib/puppet/provider/mongodb.rb index 8d0464679..6bbc350b6 100644 --- a/lib/puppet/provider/mongodb.rb +++ b/lib/puppet/provider/mongodb.rb @@ -1,4 +1,5 @@ require 'yaml' +require 'json' class Puppet::Provider::Mongodb < Puppet::Provider # Without initvars commands won't work. @@ -74,9 +75,44 @@ def self.get_conn_string "#{ip_real}:#{port_real}" end + def self.db_ismaster + cmd_ismaster = 'printjson(db.isMaster())' + if mongorc_file + cmd_ismaster = mongorc_file + cmd_ismaster + end + out = mongo(['admin', '--quiet', '--host', get_conn_string, '--eval', cmd_ismaster]) + out.gsub!(/ObjectId\(([^)]*)\)/, '\1') + out.gsub!(/ISODate\((.+?)\)/, '\1 ') + out.gsub!(/^Error\:.+/, '') + res = JSON.parse out + + return res['ismaster'] + end + + def db_ismaster + self.class.db_ismaster + end + + def self.auth_enabled + auth_enabled = false + file = get_mongod_conf_file + config = YAML.load_file(file) + if config.kind_of?(Hash) + auth_enabled = config['security.authorization'] + else # It has to be a key-value store + config = {} + File.readlines(file).collect do |line| + k,v = line.split('=') + config[k.rstrip] = v.lstrip.chomp if k and v + end + auth_enabled = config['auth'] + end + return auth_enabled + end + # Mongo Command Wrapper - def self.mongo_eval(cmd, db = 'admin') - retry_count = 10 + def self.mongo_eval(cmd, db = 'admin', retries = 10, host = nil) + retry_count = retries retry_sleep = 3 if mongorc_file cmd = mongorc_file + cmd @@ -85,9 +121,13 @@ def self.mongo_eval(cmd, db = 'admin') out = nil retry_count.times do |n| begin - out = mongo([db, '--quiet', '--host', get_conn_string, '--eval', cmd]) + if host + out = mongo([db, '--quiet', '--host', host, '--eval', cmd]) + else + out = mongo([db, '--quiet', '--host', get_conn_string, '--eval', cmd]) + end rescue => e - debug "Request failed: '#{e.message}' Retry: '#{n}'" + Puppet.debug "Request failed: '#{e.message}' Retry: '#{n}'" sleep retry_sleep next end @@ -95,15 +135,16 @@ def self.mongo_eval(cmd, db = 'admin') end if !out - fail "Could not evalute MongoDB shell command: #{cmd}" + raise Puppet::ExecutionFailure, "Could not evalute MongoDB shell command: #{cmd}" end out.gsub!(/ObjectId\(([^)]*)\)/, '\1') + out.gsub!(/^Error\:.+/, '') out end - def mongo_eval(cmd, db = 'admin') - self.class.mongo_eval(cmd, db) + def mongo_eval(cmd, db = 'admin', retries = 10, host = nil) + self.class.mongo_eval(cmd, db, retries, host) end # Mongo Version checker diff --git a/lib/puppet/provider/mongodb_database/mongodb.rb b/lib/puppet/provider/mongodb_database/mongodb.rb index 0fb9d1856..895276b0a 100644 --- a/lib/puppet/provider/mongodb_database/mongodb.rb +++ b/lib/puppet/provider/mongodb_database/mongodb.rb @@ -26,11 +26,19 @@ def self.prefetch(resources) end def create - mongo_eval('db.dummyData.insert({"created_by_puppet": 1})', @resource[:name]) + if db_ismaster + mongo_eval('db.dummyData.insert({"created_by_puppet": 1})', @resource[:name]) + else + Puppet.warning 'Database creation is available only from master host' + end end def destroy - mongo_eval('db.dropDatabase()', @resource[:name]) + if db_ismaster + mongo_eval('db.dropDatabase()', @resource[:name]) + else + Puppet.warning 'Database removal is available only from master host' + end end def exists? diff --git a/lib/puppet/provider/mongodb_replset/mongo.rb b/lib/puppet/provider/mongodb_replset/mongo.rb index 0889e4dd5..035849e14 100644 --- a/lib/puppet/provider/mongodb_replset/mongo.rb +++ b/lib/puppet/provider/mongodb_replset/mongo.rb @@ -15,8 +15,6 @@ false end - commands :mongo => 'mongo' - mk_resource_methods def initialize(resource={}) @@ -67,23 +65,27 @@ def flush private def db_ismaster(host) - mongo_command("db.isMaster()", host) + mongo_command('db.isMaster()', host) end def rs_initiate(conf, master) - return mongo_command("rs.initiate(#{conf})", master) + if auth_enabled + return mongo_command("rs.initiate(#{conf})", initialize_host) + else + return mongo_command("rs.initiate(#{conf})", master) + end end def rs_status(host) - mongo_command("rs.status()", host) + mongo_command('rs.status()', host) end def rs_add(host, master) - mongo_command("rs.add(\"#{host}\")", master) + mongo_command("rs.add('#{host}')", master) end def rs_remove(host, master) - mongo_command("rs.remove(\"#{host}\")", master) + mongo_command("rs.remove('#{host}')", master) end def rs_arbiter @@ -91,7 +93,15 @@ def rs_arbiter end def rs_add_arbiter(host, master) - mongo_command("rs.addArb(\"#{host}\")", master) + mongo_command("rs.addArb('#{host}')", master) + end + + def auth_enabled + self.class.auth_enabled + end + + def initialize_host + @resource[:initialize_host] end def master_host(hosts) @@ -104,17 +114,7 @@ def master_host(hosts) false end - def self.get_mongod_conf_file - if File.exists? '/etc/mongod.conf' - file = '/etc/mongod.conf' - else - file = '/etc/mongodb.conf' - end - file - end - def self.get_replset_properties - conn_string = get_conn_string output = mongo_command('rs.conf()', conn_string) if output['members'] @@ -135,6 +135,7 @@ def self.get_replset_properties end def alive_members(hosts) + alive = [] hosts.select do |host| begin Puppet.debug "Checking replicaset member #{host} ..." @@ -142,6 +143,12 @@ def alive_members(hosts) if status.has_key?('errmsg') and status['errmsg'] == 'not running with --replSet' raise Puppet::Error, "Can't configure replicaset #{self.name}, host #{host} is not supposed to be part of a replicaset." end + + if auth_enabled and status.has_key?('errmsg') and (status['errmsg'].include? "unauthorized" or status['errmsg'].include? "not authorized") + Puppet.warning "Host #{host} is available, but you are unauthorized because of authentication is enabled: #{auth_enabled}" + alive.push(host) + end + if status.has_key?('set') if status['set'] != self.name raise Puppet::Error, "Can't configure replicaset #{self.name}, host #{host} is already part of another replicaset." @@ -149,17 +156,16 @@ def alive_members(hosts) # This node is alive and supposed to be a member of our set Puppet.debug "Host #{host} is available for replset #{status['set']}" - true + alive.push(host) elsif status.has_key?('info') Puppet.debug "Host #{host} is alive but unconfigured: #{status['info']}" - true + alive.push(host) end rescue Puppet::ExecutionFailure Puppet.warning "Can't connect to replicaset member #{host}." - - false end end + return alive end def set_members @@ -176,14 +182,14 @@ def set_members # Find the alive members so we don't try to add dead members to the replset alive_hosts = alive_members(@property_flush[:members]) dead_hosts = @property_flush[:members] - alive_hosts - raise Puppet::Error, "Can't connect to any member of replicaset #{self.name}." if alive_hosts.empty? Puppet.debug "Alive members: #{alive_hosts.inspect}" Puppet.debug "Dead members: #{dead_hosts.inspect}" unless dead_hosts.empty? + raise Puppet::Error, "Can't connect to any member of replicaset #{self.name}." if alive_hosts.empty? else alive_hosts = [] end - if @property_flush[:ensure] == :present and @property_hash[:ensure] != :present + if @property_flush[:ensure] == :present and @property_hash[:ensure] != :present and !master_host(alive_hosts) Puppet.debug "Initializing the replset #{self.name}" # Create a replset configuration @@ -201,12 +207,35 @@ def set_members if output['ok'] == 0 raise Puppet::Error, "rs.initiate() failed for replicaset #{self.name}: #{output['errmsg']}" end + + # Check that the replicaset has finished initialization + retry_limit = 10 + retry_sleep = 3 + + retry_limit.times do |n| + begin + if db_ismaster(alive_hosts[0])['ismaster'] + Puppet.debug 'Replica set initialization has successfully ended' + return + else + Puppet.debug "Wainting for replica initialization. Retry: #{n}" + sleep retry_sleep + next + end + end + end + raise Puppet::Error, "rs.initiate() failed for replicaset #{self.name}: host #{alive_hosts[0]} didn't become master" + else # Add members to an existing replset + Puppet.debug "Adding member to existing replset #{self.name}" if master = master_host(alive_hosts) - current_hosts = db_ismaster(master)['hosts'] + master_data = db_ismaster(master) + current_hosts = master_data['hosts'] + current_hosts = current_hosts + master_data['arbiters'] if master_data.has_key?('arbiters') Puppet.debug "Current Hosts are: #{current_hosts.inspect}" newhosts = alive_hosts - current_hosts + Puppet.debug "New Hosts are: #{newhosts.inspect}" newhosts.each do |host| output = {} if rs_arbiter == host @@ -225,39 +254,27 @@ def set_members end def mongo_command(command, host, retries=4) - self.class.mongo_command(command,host,retries) + self.class.mongo_command(command, host, retries) end def self.mongo_command(command, host=nil, retries=4) - # Allow waiting for mongod to become ready - # Wait for 2 seconds initially and double the delay at each retry - wait = 2 begin - args = Array.new - args << '--quiet' - args << ['--host',host] if host - args << ['--eval',"printjson(#{command})"] - output = mongo(args.flatten) + output = mongo_eval("printjson(#{command})", 'admin', retries, host) rescue Puppet::ExecutionFailure => e - if e =~ /Error: couldn't connect to server/ and wait <= 2**max_wait - info("Waiting #{wait} seconds for mongod to become available") - sleep wait - wait *= 2 - retry - else - raise - end + Puppet.debug "Got an exception: #{e}" + raise end # Dirty hack to remove JavaScript objects output.gsub!(/ISODate\((.+?)\)/, '\1 ') output.gsub!(/Timestamp\((.+?)\)/, '[\1]') - output.gsub!(/ObjectId\(([^)]*)\)/, '\1') #Hack to avoid non-json empty sets output = "{}" if output == "null\n" + # Parse the JSON output and return JSON.parse(output) + end end diff --git a/lib/puppet/provider/mongodb_user/mongodb.rb b/lib/puppet/provider/mongodb_user/mongodb.rb index 27502572c..a960e9a92 100644 --- a/lib/puppet/provider/mongodb_user/mongodb.rb +++ b/lib/puppet/provider/mongodb_user/mongodb.rb @@ -8,35 +8,40 @@ def self.instances require 'json' - if mongo_24? - dbs = JSON.parse mongo_eval('printjson(db.getMongo().getDBs()["databases"].map(function(db){return db["name"]}))') || 'admin' - - allusers = [] - - dbs.each do |db| - users = JSON.parse mongo_eval('printjson(db.system.users.find().toArray())', db) + if db_ismaster + if mongo_24? + dbs = JSON.parse mongo_eval('printjson(db.getMongo().getDBs()["databases"].map(function(db){return db["name"]}))') || 'admin' + + allusers = [] + + dbs.each do |db| + users = JSON.parse mongo_eval('printjson(db.system.users.find().toArray())', db) + + allusers += users.collect do |user| + new(:name => user['_id'], + :ensure => :present, + :username => user['user'], + :database => db, + :roles => user['roles'].sort, + :password_hash => user['pwd']) + end + end + return allusers + else + users = JSON.parse mongo_eval('printjson(db.system.users.find().toArray())') - allusers += users.collect do |user| + users.collect do |user| new(:name => user['_id'], :ensure => :present, :username => user['user'], - :database => db, - :roles => user['roles'].sort, - :password_hash => user['pwd']) + :database => user['db'], + :roles => from_roles(user['roles'], user['db']), + :password_hash => user['credentials']['MONGODB-CR']) end end - return allusers else - users = JSON.parse mongo_eval('printjson(db.system.users.find().toArray())') - - users.collect do |user| - new(:name => user['_id'], - :ensure => :present, - :username => user['user'], - :database => user['db'], - :roles => from_roles(user['roles'], user['db']), - :password_hash => user['credentials']['MONGODB-CR']) - end + Puppet.warning 'User info is available only from master host' + return [] end end @@ -53,45 +58,51 @@ def self.prefetch(resources) mk_resource_methods def create + if db_ismaster + if mongo_24? + user = { + :user => @resource[:username], + :pwd => @resource[:password_hash], + :roles => @resource[:roles] + } + + mongo_eval("db.addUser(#{user.to_json})", @resource[:database]) + else + cmd_json=<<-EOS.gsub(/^\s*/, '').gsub(/$\n/, '') + { + "createUser": "#{@resource[:username]}", + "pwd": "#{@resource[:password_hash]}", + "customData": {"createdBy": "Puppet Mongodb_user['#{@resource[:name]}']"}, + "roles": #{@resource[:roles].to_json}, + "digestPassword": false + } + EOS + + mongo_eval("db.runCommand(#{cmd_json})", @resource[:database]) + end + @property_hash[:ensure] = :present + @property_hash[:username] = @resource[:username] + @property_hash[:database] = @resource[:database] + @property_hash[:password_hash] = '' + @property_hash[:rolse] = @resource[:roles] - if mongo_24? - user = { - :user => @resource[:username], - :pwd => @resource[:password_hash], - :roles => @resource[:roles] - } - - mongo_eval("db.addUser(#{user.to_json})", @resource[:database]) + exists? ? (return true) : (return false) else - cmd_json=<<-EOS.gsub(/^\s*/, '').gsub(/$\n/, '') - { - "createUser": "#{@resource[:username]}", - "pwd": "#{@resource[:password_hash]}", - "customData": {"createdBy": "Puppet Mongodb_user['#{@resource[:name]}']"}, - "roles": #{@resource[:roles].to_json}, - "digestPassword": false - } - EOS - - mongo_eval("db.runCommand(#{cmd_json})", @resource[:database]) + Puppet.warning 'User creation is available only from master host' end - - @property_hash[:ensure] = :present - @property_hash[:username] = @resource[:username] - @property_hash[:database] = @resource[:database] - @property_hash[:password_hash] = '' - @property_hash[:rolse] = @resource[:roles] - - exists? ? (return true) : (return false) end def destroy - if mongo_24? - mongo_eval("db.removeUser('#{@resource[:username]}')") + if db_ismaster + if mongo_24? + mongo_eval("db.removeUser('#{@resource[:username]}')") + else + mongo_eval("db.dropUser('#{@resource[:username]}')") + end else - mongo_eval("db.dropUser('#{@resource[:username]}')") + Puppet.warning 'User removal is available only from master host' end end @@ -100,30 +111,37 @@ def exists? end def password_hash=(value) - cmd_json=<<-EOS.gsub(/^\s*/, '').gsub(/$\n/, '') - { - "updateUser": "#{@resource[:username]}", - "pwd": "#{@resource[:password_hash]}", - "digestPassword": false - } - EOS - - mongo_eval("db.runCommand(#{cmd_json})", @resource[:database]) + if db_ismaster + cmd_json=<<-EOS.gsub(/^\s*/, '').gsub(/$\n/, '') + { + "updateUser": "#{@resource[:username]}", + "pwd": "#{@resource[:password_hash]}", + "digestPassword": false + } + EOS + mongo_eval("db.runCommand(#{cmd_json})", @resource[:database]) + else + Puppet.warning 'User password operations are available only from master host' + end end def roles=(roles) - if mongo_24? - mongo_eval("db.system.users.update({user:'#{@resource[:username]}'}, { $set: {roles: #{@resource[:roles].to_json}}})") - else - grant = roles-@resource[:roles] - if grant.length > 0 - mongo_eval("db.getSiblingDB('#{@resource[:database]}').grantRolesToUser('#{@resource[:username]}', #{grant. to_json})") - end + if db_ismaster + if mongo_24? + mongo_eval("db.system.users.update({user:'#{@resource[:username]}'}, { $set: {roles: #{@resource[:roles].to_json}}})") + else + grant = roles-@resource[:roles] + if grant.length > 0 + mongo_eval("db.getSiblingDB('#{@resource[:database]}').grantRolesToUser('#{@resource[:username]}', #{grant. to_json})") + end - revoke = @resource[:roles]-roles - if revoke.length > 0 - mongo_eval("db.getSiblingDB('#{@resource[:database]}').revokeRolesFromUser('#{@resource[:username]}', #{revoke.to_json})") + revoke = @resource[:roles]-roles + if revoke.length > 0 + mongo_eval("db.getSiblingDB('#{@resource[:database]}').revokeRolesFromUser('#{@resource[:username]}', #{revoke.to_json})") + end end + else + Puppet.warning 'User roles operations are available only from master host' end end diff --git a/lib/puppet/type/mongodb_replset.rb b/lib/puppet/type/mongodb_replset.rb index a78e24042..3717b2439 100644 --- a/lib/puppet/type/mongodb_replset.rb +++ b/lib/puppet/type/mongodb_replset.rb @@ -21,6 +21,11 @@ desc "The replicaSet arbiter" end + newparam(:initialize_host) do + desc "Host to use for Replicaset initialization" + defaultto '127.0.0.1' + end + newproperty(:members, :array_matching => :all) do desc "The replicaSet members" diff --git a/manifests/db.pp b/manifests/db.pp index a9894ff57..3cc3eedb5 100644 --- a/manifests/db.pp +++ b/manifests/db.pp @@ -19,9 +19,8 @@ ) { mongodb_database { $name: - ensure => present, - tries => $tries, - require => Class['mongodb::server'], + ensure => present, + tries => $tries } if $password_hash { diff --git a/manifests/params.pp b/manifests/params.pp index 7fe6bec99..0926f3946 100644 --- a/manifests/params.pp +++ b/manifests/params.pp @@ -8,6 +8,10 @@ $service_ensure = pick($mongodb::globals::service_ensure, 'running') $service_status = $mongodb::globals::service_status $restart = true + $create_admin = false + $admin_username = 'admin' + $store_creds = false + $rcfile = "${::root_home}/.mongorc.js" $mongos_service_manage = pick($mongodb::globals::mongos_service_manage, true) $mongos_service_enable = pick($mongodb::globals::mongos_service_enable, true) diff --git a/manifests/replset.pp b/manifests/replset.pp index ce4a02555..0c6cc2929 100644 --- a/manifests/replset.pp +++ b/manifests/replset.pp @@ -7,4 +7,9 @@ if $sets { create_resources(mongodb_replset, $sets) } + + # Order replset before any DB's and shard config + Mongodb_replset <| |> -> Mongodb_database <| |> + Mongodb_replset <| |> -> Mongodb_shard <| |> + Mongodb_replset <| |> -> Mongodb_user <| |> } diff --git a/manifests/server.pp b/manifests/server.pp index 555c7970e..e51668e2f 100644 --- a/manifests/server.pp +++ b/manifests/server.pp @@ -8,6 +8,7 @@ $config = $mongodb::params::config, $dbpath = $mongodb::params::dbpath, $pidfilepath = $mongodb::params::pidfilepath, + $rcfile = $mongodb::params::rcfile, $service_manage = $mongodb::params::service_manage, $service_provider = $mongodb::params::service_provider, @@ -51,6 +52,8 @@ $mms_name = undef, $mms_interval = undef, $replset = undef, + $replset_config = undef, + $replset_members = undef, $configsvr = undef, $shardsvr = undef, $rest = undef, @@ -67,6 +70,16 @@ $restart = $mongodb::params::restart, $storage_engine = undef, + $create_admin = $mongodb::params::create_admin, + $admin_username = $mongodb::params::admin_username, + $admin_password = undef, + $store_creds = $mongodb::params::store_creds, + $admin_roles = ['userAdmin', 'readWrite', 'dbAdmin', + 'dbAdminAnyDatabase', 'readAnyDatabase', + 'readWriteAnyDatabase', 'userAdminAnyDatabase', + 'clusterAdmin', 'clusterManager', 'clusterMonitor', + 'hostManager', 'root', 'restore'], + # Deprecated parameters $master = undef, $slave = undef, @@ -102,4 +115,61 @@ class { '::mongodb::server::install': }-> anchor { 'mongodb::server::end': } } + + if $create_admin { + validate_string($admin_password) + + mongodb::db { 'admin': + user => $admin_username, + password => $admin_password, + roles => $admin_roles + } + + # Make sure it runs at the correct point + Anchor['mongodb::server::end'] -> Mongodb::Db['admin'] + + # Make sure it runs before other DB creation + Mongodb::Db['admin'] -> Mongodb::Db <| title != 'admin' |> + } + + # Set-up replicasets + if $replset { + # Check that we've got either a members array or a replset_config hash + if $replset_members and $replset_config { + fail('You can provide either replset_members or replset_config, not both.') + } elsif !$replset_members and !$replset_config { + # No members or config provided. Warn about it. + warning('Replset specified, but no replset_members or replset_config provided.') + } else { + if $replset_config { + validate_hash($replset_config) + + # Copy it to REAL value + $replset_config_REAL = $replset_config + + } else { + validate_array($replset_members) + + # Build up a config hash + $replset_config_REAL = { + "${replset}" => { + 'ensure' => 'present', + 'members' => $replset_members + } + } + } + + # Wrap the replset class + class { 'mongodb::replset': + sets => $replset_config_REAL + } + Anchor['mongodb::server::end'] -> Class['mongodb::replset'] + + # Make sure that the ordering is correct + if $create_admin { + Class['mongodb::replset'] -> Mongodb::Db['admin'] + } + + } + } } diff --git a/manifests/server/config.pp b/manifests/server/config.pp index 9a146b4eb..6c9df389e 100644 --- a/manifests/server/config.pp +++ b/manifests/server/config.pp @@ -18,6 +18,11 @@ $cpu = $mongodb::server::cpu $auth = $mongodb::server::auth $noath = $mongodb::server::noauth + $create_admin = $mongodb::server::create_admin + $admin_username = $mongodb::server::admin_username + $admin_password = $mongodb::server::admin_password + $store_creds = $mongodb::server::store_creds + $rcfile = $mongodb::server::rcfile $verbose = $mongodb::server::verbose $verbositylevel = $mongodb::server::verbositylevel $objcheck = $mongodb::server::objcheck @@ -211,4 +216,18 @@ ensure => absent } } + + if $auth and $store_creds { + file { $rcfile: + ensure => present, + content => template('mongodb/mongorc.js.erb'), + owner => 'root', + group => 'root', + mode => '0644' + } + } else { + file { $rcfile: + ensure => absent + } + } } diff --git a/spec/acceptance/replset_spec.rb b/spec/acceptance/replset_spec.rb index 8be5a0f46..84896706b 100644 --- a/spec/acceptance/replset_spec.rb +++ b/spec/acceptance/replset_spec.rb @@ -9,10 +9,14 @@ pp = <<-EOS class { 'mongodb::globals': } -> class { 'mongodb::server': - ensure => purged, + ensure => absent, + package_ensure => absent, + service_ensure => stopped } if $::osfamily == 'RedHat' { - class { 'mongodb::client': } + class { 'mongodb::client': + ensure => absent + } } EOS @@ -66,4 +70,133 @@ class { 'mongodb::client': } end end end + + describe 'mongodb_replset resource with auth => true' do + after :all do + # Have to drop the DB to disable replsets for further testing + on hosts, %{mongo local --verbose --eval 'db.dropDatabase()'} + + pp = <<-EOS + class { 'mongodb::globals': } + -> class { 'mongodb::server': + ensure => absent, + package_ensure => absent, + service_ensure => stopped + } + if $::osfamily == 'RedHat' { + class { 'mongodb::client': + ensure => absent + } + } + EOS + + apply_manifest_on(hosts.reverse, pp, :catch_failures => true) + end + + it 'configures mongo on both nodes' do + pp = <<-EOS + class { 'mongodb::globals': + version => '2.6.9-1', + manage_package_repo => true + } -> + class { 'mongodb::server': + admin_username => 'admin', + admin_password => 'password', + auth => true, + bind_ip => '0.0.0.0', + replset => 'test', + keyfile => '/var/lib/mongodb/mongodb-keyfile', + key => '+dxlTrury7xtD0FRqFf3YWGnKqWAtlyauuemxuYuyz9POPUuX1Uj3chGU8MFMHa7 +UxASqex7NLMALQXHL+Th4T3dyb6kMZD7KiMcJObO4M+JLiX9drcTiifsDEgGMi7G +vYn3pWSm5TTDrHJw7RNWfMHw3sHk0muGQcO+0dWv3sDJ6SiU8yOKRtYcTEA15GbP +ReDZuHFy1T1qhk5NIt6pTtPGsZKSm2wAWIOa2f2IXvpeQHhjxP8aDFb3fQaCAqOD +R7hrimqq0Nickfe8RLA89iPXyadr/YeNBB7w7rySatQBzwIbBUVGNNA5cxCkwyx9 +E5of3xi7GL9xNxhQ8l0JEpofd4H0y0TOfFDIEjc7cOnYlKAHzBgog4OcFSILgUaF +kHuTMtv0pj+MMkW2HkeXETNII9XE1+JiZgHY08G7yFEJy87ttUoeKmpbI6spFz5U +4K0amj+N6SOwXaS8uwp6kCqay0ERJLnw+7dKNKZIZdGCrrBxcZ7wmR/wLYrxvHhZ +QpeXTxgD5ebwCR0cf3Xnb5ql5G/HHKZDq8LTFHwELNh23URGPY7K7uK+IF6jSEhq +V2H3HnWV9teuuJ5he9BB/pLnyfjft6KUUqE9HbaGlX0f3YBk/0T3S2ESN4jnfRTQ +ysAKvQ6NasXkzqXktu8X4fS5QNqrFyqKBZSWxttfJBKXnT0TxamCKLRx4AgQglYo +3KRoyfxXx6G+AjP1frDJxFAFEIgEFqRk/FFuT/y9LpU+3cXYX1Gt6wEatgmnBM3K +g+Bybk5qHv1b7M8Tv9/I/BRXcpLHeIkMICMY8sVPGmP8xzL1L3i0cws8p5h0zPBa +YG/QX0BmltAni8owgymFuyJgvr/gaRX4WHbKFD+9nKpqJ3ocuVNuCDsxDqLsJEME +nc1ohyB0lNt8lHf1U00mtgDSV3fwo5LkwhRi6d+bDBTL/C6MZETMLdyCqDlTdUWG +YXIsJ0gYcu9XG3mx10LbdPJvxSMg' + + } + if $::osfamily == 'RedHat' { + include mongodb::client + } + EOS + + apply_manifest_on(hosts.reverse, pp, :catch_failures => true) + apply_manifest_on(hosts.reverse, pp, :catch_changes => true) + end + + it 'sets up the replset with puppet' do + pp = <<-EOS + class { 'mongodb::globals': + version => '2.6.9-1', + manage_package_repo => true + } -> + class { 'mongodb::server': + create_admin => true, + admin_username => 'admin', + admin_password => 'password', + auth => true, + bind_ip => '0.0.0.0', + replset => 'test', + keyfile => '/var/lib/mongodb/mongodb-keyfile', + key => '+dxlTrury7xtD0FRqFf3YWGnKqWAtlyauuemxuYuyz9POPUuX1Uj3chGU8MFMHa7 +UxASqex7NLMALQXHL+Th4T3dyb6kMZD7KiMcJObO4M+JLiX9drcTiifsDEgGMi7G +vYn3pWSm5TTDrHJw7RNWfMHw3sHk0muGQcO+0dWv3sDJ6SiU8yOKRtYcTEA15GbP +ReDZuHFy1T1qhk5NIt6pTtPGsZKSm2wAWIOa2f2IXvpeQHhjxP8aDFb3fQaCAqOD +R7hrimqq0Nickfe8RLA89iPXyadr/YeNBB7w7rySatQBzwIbBUVGNNA5cxCkwyx9 +E5of3xi7GL9xNxhQ8l0JEpofd4H0y0TOfFDIEjc7cOnYlKAHzBgog4OcFSILgUaF +kHuTMtv0pj+MMkW2HkeXETNII9XE1+JiZgHY08G7yFEJy87ttUoeKmpbI6spFz5U +4K0amj+N6SOwXaS8uwp6kCqay0ERJLnw+7dKNKZIZdGCrrBxcZ7wmR/wLYrxvHhZ +QpeXTxgD5ebwCR0cf3Xnb5ql5G/HHKZDq8LTFHwELNh23URGPY7K7uK+IF6jSEhq +V2H3HnWV9teuuJ5he9BB/pLnyfjft6KUUqE9HbaGlX0f3YBk/0T3S2ESN4jnfRTQ +ysAKvQ6NasXkzqXktu8X4fS5QNqrFyqKBZSWxttfJBKXnT0TxamCKLRx4AgQglYo +3KRoyfxXx6G+AjP1frDJxFAFEIgEFqRk/FFuT/y9LpU+3cXYX1Gt6wEatgmnBM3K +g+Bybk5qHv1b7M8Tv9/I/BRXcpLHeIkMICMY8sVPGmP8xzL1L3i0cws8p5h0zPBa +YG/QX0BmltAni8owgymFuyJgvr/gaRX4WHbKFD+9nKpqJ3ocuVNuCDsxDqLsJEME +nc1ohyB0lNt8lHf1U00mtgDSV3fwo5LkwhRi6d+bDBTL/C6MZETMLdyCqDlTdUWG +YXIsJ0gYcu9XG3mx10LbdPJvxSMg' + } + if $::osfamily == 'RedHat' { + include mongodb::client + } + mongodb_replset { 'test': + auth_enabled => true, + members => [#{hosts.collect{|x|"'#{x}:27017'"}.join(',')}], + before => Mongodb_user['admin'] + } + EOS + apply_manifest_on(hosts_as('master'), pp, :catch_failures => true) + apply_manifest_on(hosts_as('master'), pp, :catch_changes => true) + on(hosts_as('master'), 'mongo --quiet --eval "load(\'/root/.mongorc.js\');printjson(rs.conf())"') do |r| + expect(r.stdout).to match /#{hosts[0]}:27017/ + expect(r.stdout).to match /#{hosts[1]}:27017/ + end + end + + it 'inserts data on the master' do + sleep(30) + on hosts_as('master'), %{mongo test --verbose --eval 'load("/root/.mongorc.js");db.dummyData.insert({"created_by_puppet": 1})'} + end + + it 'checks the data on the master' do + on hosts_as('master'), %{mongo test --verbose --eval 'load("/root/.mongorc.js");printjson(db.dummyData.findOne())'} do |r| + expect(r.stdout).to match /created_by_puppet/ + end + end + + it 'checks the data on the slave' do + sleep(10) + on hosts_as('slave'), %{mongo test --verbose --eval 'load("/root/.mongorc.js");rs.slaveOk();printjson(db.dummyData.findOne())'} do |r| + expect(r.stdout).to match /created_by_puppet/ + end + end + end end diff --git a/spec/acceptance/server_spec.rb b/spec/acceptance/server_spec.rb index ab4686aef..a8dee974e 100644 --- a/spec/acceptance/server_spec.rb +++ b/spec/acceptance/server_spec.rb @@ -117,6 +117,119 @@ class {'mongodb::globals': manage_package_repo => #{tengen}, } end end + shared_examples 'auth tests' do |tengen| + if tengen + case fact('osfamily') + when 'RedHat' + package_name = 'mongodb-org-server' + service_name = 'mongod' + config_file = '/etc/mongod.conf' + when 'Debian' + package_name = 'mongodb-org-server' + service_name = 'mongod' + config_file = '/etc/mongod.conf' + end + else + case fact('osfamily') + when 'RedHat' + package_name = 'mongodb-server' + service_name = 'mongod' + config_file = '/etc/mongodb.conf' + when 'Debian' + package_name = 'mongodb-server' + service_name = 'mongodb' + config_file = '/etc/mongodb.conf' + end + end + + client_name = 'mongo --version' + + auth_enabled = 'mongo --quiet --eval "db.serverCmdLineOpts().code"' + admin_login = "mongo admin --quiet --eval \"load('/root/.mongorc.js');printjson(db.getUser('admin')['customData'])\"" + + context "default parameters with 10gen => #{tengen} and auth => true" do + it 'should work with no errors with authentication enabled' do + pp = <<-EOS + class { 'mongodb::globals': manage_package_repo => #{tengen}, } + -> class { 'mongodb::server': + auth => true, + create_admin => true, + store_creds => true, + admin_username => 'admin', + admin_password => 'password' + } + class { 'mongodb::client': } + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + end + + describe package(package_name) do + it { is_expected.to be_installed } + end + + describe file(config_file) do + it { is_expected.to be_file } + end + + describe service(service_name) do + it { is_expected.to be_enabled } + it { is_expected.to be_running } + end + + describe port(27017) do + it { is_expected.to be_listening } + end + + describe command(auth_enabled) do + describe '#stdout' do + subject { super().stdout.strip } + it { is_expected.to eq "13" } + end + end + + describe file('/root/.mongorc.js') do + it { is_expected.to be_file } + it { is_expected.to be_owned_by 'root' } + it { is_expected.to be_grouped_into 'root' } + it { is_expected.to be_mode 644 } + it { is_expected.to contain 'db.auth(\'admin\', \'password\')' } + end + + describe command(admin_login) do + describe '#stdout' do + subject { super().stdout.strip } + it { is_expected.to match "{ \"createdBy\" : \"Puppet Mongodb_user['User admin on db admin']\" }"} + end + end + + describe command(client_name) do + describe '#exit_status' do + subject { super().exit_status } + it { is_expected.to eq 0 } + end + end + end + + describe "uninstalling with 10gen => #{tengen}" do + it 'uninstalls mongodb' do + pp = <<-EOS + class {'mongodb::globals': manage_package_repo => #{tengen}, } + -> class { 'mongodb::server': + ensure => absent, + package_ensure => absent, + service_ensure => stopped, + service_enable => false + } + -> class { 'mongodb::client': ensure => absent, } + EOS + apply_manifest(pp, :catch_failures => true) + end + end + end + it_behaves_like 'normal tests', false it_behaves_like 'normal tests', true + it_behaves_like 'auth tests', true end diff --git a/spec/classes/server_config_spec.rb b/spec/classes/server_config_spec.rb index dbecb8707..e630fc12d 100644 --- a/spec/classes/server_config_spec.rb +++ b/spec/classes/server_config_spec.rb @@ -3,7 +3,7 @@ describe 'mongodb::server::config', :type => :class do describe 'with preseted variables' do - let(:pre_condition) { ["class mongodb::server { $config = '/etc/mongod.conf' $dbpath = '/var/lib/mongo' }", "include mongodb::server"]} + let(:pre_condition) { ["class mongodb::server { $config = '/etc/mongod.conf' $dbpath = '/var/lib/mongo' $rcfile = '/root/.mongorc.js' }", "include mongodb::server"]} it { is_expected.to contain_file('/etc/mongod.conf') @@ -12,7 +12,7 @@ end describe 'with default values' do - let(:pre_condition) {[ "class mongodb::server { $config = '/etc/mongod.conf' $dbpath = '/var/lib/mongo' $ensure = present $user = 'mongod' $group = 'mongod' $port = 29017 $bind_ip = ['0.0.0.0'] $fork = true $logpath ='/var/log/mongo/mongod.log' $logappend = true }", "include mongodb::server" ]} + let(:pre_condition) {[ "class mongodb::server { $config = '/etc/mongod.conf' $dbpath = '/var/lib/mongo' $create_admin = false $rcfile = '/root/.mongorc.js' $store_creds = true $ensure = present $user = 'mongod' $group = 'mongod' $port = 29017 $bind_ip = ['0.0.0.0'] $fork = true $logpath ='/var/log/mongo/mongod.log' $logappend = true }", "include mongodb::server" ]} it { is_expected.to contain_file('/etc/mongod.conf').with({ @@ -27,11 +27,13 @@ is_expected.to contain_file('/etc/mongod.conf').with_content(/^logappend=true/) is_expected.to contain_file('/etc/mongod.conf').with_content(/^logpath=\/var\/log\/mongo\/mongod\.log/) is_expected.to contain_file('/etc/mongod.conf').with_content(/^fork=true/) + + is_expected.to contain_file('/root/.mongorc.js').with({ :ensure => 'absent' }) } end describe 'with absent ensure' do - let(:pre_condition) { ["class mongodb::server { $config = '/etc/mongod.conf' $dbpath = '/var/lib/mongo' $ensure = absent }", "include mongodb::server"]} + let(:pre_condition) { ["class mongodb::server { $config = '/etc/mongod.conf' $dbpath = '/var/lib/mongo' $rcfile = '/root/.mongorc.js' $ensure = absent }", "include mongodb::server"]} it { is_expected.to contain_file('/etc/mongod.conf').with({ :ensure => 'absent' }) @@ -40,7 +42,7 @@ end describe 'when specifying storage_engine' do - let(:pre_condition) { ["class mongodb::server { $config = '/etc/mongod.conf' $dbpath = '/var/lib/mongo' $ensure = present $version='3.0.3' $storage_engine = 'SomeEngine' $storage_engine_internal = 'SomeEngine' $user = 'mongod' $group = 'mongod' $port = 29017 $bind_ip = ['0.0.0.0'] $fork = true $logpath ='/var/log/mongo/mongod.log' $logappend = true}", "include mongodb::server"]} + let(:pre_condition) { ["class mongodb::server { $config = '/etc/mongod.conf' $dbpath = '/var/lib/mongo' $rcfile = '/root/.monogrc.js' $ensure = present $version='3.0.3' $storage_engine = 'SomeEngine' $storage_engine_internal = 'SomeEngine' $user = 'mongod' $group = 'mongod' $port = 29017 $bind_ip = ['0.0.0.0'] $fork = true $logpath ='/var/log/mongo/mongod.log' $logappend = true}", "include mongodb::server"]} it { is_expected.to contain_file('/etc/mongod.conf').with_content(/storage.engine:\sSomeEngine/) @@ -48,7 +50,7 @@ end describe 'with specific bind_ip values and ipv6' do - let(:pre_condition) { ["class mongodb::server { $config = '/etc/mongod.conf' $dbpath = '/var/lib/mongo' $ensure = present $bind_ip = ['127.0.0.1', 'fd00:beef:dead:55::143'] $ipv6 = true }", "include mongodb::server"]} + let(:pre_condition) { ["class mongodb::server { $config = '/etc/mongod.conf' $dbpath = '/var/lib/mongo' $rcfile = '/root/.mongorc.js' $ensure = present $bind_ip = ['127.0.0.1', 'fd00:beef:dead:55::143'] $ipv6 = true }", "include mongodb::server"]} it { is_expected.to contain_file('/etc/mongod.conf').with_content(/bind_ip\s=\s127\.0\.0\.1\,fd00:beef:dead:55::143/) @@ -57,7 +59,7 @@ end describe 'with specific bind_ip values' do - let(:pre_condition) { ["class mongodb::server { $config = '/etc/mongod.conf' $dbpath = '/var/lib/mongo' $ensure = present $bind_ip = ['127.0.0.1', '10.1.1.13']}", "include mongodb::server"]} + let(:pre_condition) { ["class mongodb::server { $config = '/etc/mongod.conf' $dbpath = '/var/lib/mongo' $rcfile = '/root/.mongorc.js' $ensure = present $bind_ip = ['127.0.0.1', '10.1.1.13']}", "include mongodb::server"]} it { is_expected.to contain_file('/etc/mongod.conf').with_content(/bind_ip\s=\s127\.0\.0\.1\,10\.1\.1\.13/) @@ -65,15 +67,16 @@ end describe 'when specifying auth to true' do - let(:pre_condition) { ["class mongodb::server { $config = '/etc/mongod.conf' $auth = true $dbpath = '/var/lib/mongo' $ensure = present }", "include mongodb::server"]} + let(:pre_condition) { ["class mongodb::server { $config = '/etc/mongod.conf' $auth = true $dbpath = '/var/lib/mongo' $rcfile = '/root/.mongorc.js' $ensure = present }", "include mongodb::server"]} it { is_expected.to contain_file('/etc/mongod.conf').with_content(/^auth=true/) + is_expected.to contain_file('/root/.mongorc.js') } end describe 'when specifying set_parameter value' do - let(:pre_condition) { ["class mongodb::server { $config = '/etc/mongod.conf' $set_parameter = 'textSearchEnable=true' $dbpath = '/var/lib/mongo' $ensure = present }", "include mongodb::server"]} + let(:pre_condition) { ["class mongodb::server { $config = '/etc/mongod.conf' $set_parameter = 'textSearchEnable=true' $dbpath = '/var/lib/mongo' $rcfile = '/root/.mongorc.js' $ensure = present }", "include mongodb::server"]} it { is_expected.to contain_file('/etc/mongod.conf').with_content(/^setParameter = textSearchEnable=true/) @@ -82,7 +85,7 @@ describe 'with journal:' do context 'on true with i686 architecture' do - let(:pre_condition) { ["class mongodb::server { $config = '/etc/mongod.conf' $dbpath = '/var/lib/mongo' $ensure = present $journal = true }", "include mongodb::server"]} + let(:pre_condition) { ["class mongodb::server { $config = '/etc/mongod.conf' $dbpath = '/var/lib/mongo' $rcfile = '/root/.mongorc.js' $ensure = present $journal = true }", "include mongodb::server"]} let (:facts) { { :architecture => 'i686' } } it { @@ -95,14 +98,14 @@ describe 'with quota to' do context 'true and without quotafiles' do - let(:pre_condition) { ["class mongodb::server { $config = '/etc/mongod.conf' $dbpath = '/var/lib/mongo' $ensure = present $quota = true }", "include mongodb::server"]} + let(:pre_condition) { ["class mongodb::server { $config = '/etc/mongod.conf' $dbpath = '/var/lib/mongo' $rcfile = '/root/.mongorc.js' $ensure = present $quota = true }", "include mongodb::server"]} it { is_expected.to contain_file('/etc/mongod.conf').with_content(/^quota = true/) } end context 'true and with quotafiles' do - let(:pre_condition) { ["class mongodb::server { $config = '/etc/mongod.conf' $dbpath = '/var/lib/mongo' $ensure = present $quota = true $quotafiles = 1 }", "include mongodb::server"]} + let(:pre_condition) { ["class mongodb::server { $config = '/etc/mongod.conf' $dbpath = '/var/lib/mongo' $rcfile = '/root/.mongorc.js' $ensure = present $quota = true $quotafiles = 1 }", "include mongodb::server"]} it { is_expected.to contain_file('/etc/mongod.conf').with_content(/quota = true/) @@ -113,7 +116,7 @@ describe 'when specifying syslog value' do context 'it should be set to true' do - let(:pre_condition) { ["class mongodb::server { $config = '/etc/mongod.conf' $dbpath = '/var/lib/mongo' $ensure = present $syslog = true }", "include mongodb::server"]} + let(:pre_condition) { ["class mongodb::server { $config = '/etc/mongod.conf' $dbpath = '/var/lib/mongo' $rcfile = '/root/.mongorc.js' $ensure = present $syslog = true }", "include mongodb::server"]} it { is_expected.to contain_file('/etc/mongod.conf').with_content(/^syslog = true/) @@ -121,7 +124,7 @@ end context 'if logpath is also set an error should be raised' do - let(:pre_condition) { ["class mongodb::server { $config = '/etc/mongod.conf' $dbpath = '/var/lib/mongo' $ensure = present $syslog = true $logpath ='/var/log/mongo/mongod.log' }", "include mongodb::server"]} + let(:pre_condition) { ["class mongodb::server { $config = '/etc/mongod.conf' $dbpath = '/var/lib/mongo' $rcfile = '/root/.mongorc.js' $ensure = present $syslog = true $logpath ='/var/log/mongo/mongod.log' }", "include mongodb::server"]} it { expect { is_expected.to contain_file('/etc/mongod.conf') }.to raise_error(Puppet::Error, /You cannot use syslog with logpath/) @@ -130,4 +133,27 @@ end + describe 'with store_creds' do + context 'true' do + let(:pre_condition) { ["class mongodb::server { $admin_username = 'admin' $admin_password = 'password' $auth = true $store_creds = true $config = '/etc/mongod.conf' $dbpath = '/var/lib/mongo' $rcfile = '/root/.mongorc.js' $ensure = present }", "include mongodb::server"]} + + it { + is_expected.to contain_file('/root/.mongorc.js'). + with_ensure('present'). + with_owner('root'). + with_group('root'). + with_mode('0644'). + with_content(/db.auth\('admin', 'password'\)/) + } + end + + context 'false' do + let(:pre_condition) { ["class mongodb::server { $config = '/etc/mongod.conf' $dbpath = '/var/lib/mongo' $rcfile = '/root/.mongorc.js' $ensure = present $store_creds = false }", "include mongodb::server"]} + + it { + is_expected.to contain_file('/root/.mongorc.js').with_ensure('absent') + } + end + end + end diff --git a/spec/classes/server_spec.rb b/spec/classes/server_spec.rb index 591303c08..15f34dee1 100644 --- a/spec/classes/server_spec.rb +++ b/spec/classes/server_spec.rb @@ -9,8 +9,39 @@ end context 'with defaults' do - it { is_expected.to contain_class('mongodb::server::install') } - it { is_expected.to contain_class('mongodb::server::config') } + it { is_expected.to compile.with_all_deps } + it { is_expected.to contain_class('mongodb::server::install'). + that_comes_before('Class[mongodb::server::config]') } + it { is_expected.to contain_class('mongodb::server::config'). + that_notifies('Class[mongodb::server::service]') } + it { is_expected.to contain_class('mongodb::server::service') } + end + + context 'with create_admin => true' do + let(:params) do + { + :create_admin => true, + :admin_username => 'admin', + :admin_password => 'password' + } + end + it { is_expected.to compile.with_all_deps } + it { is_expected.to contain_class('mongodb::server::install'). + that_comes_before('Class[mongodb::server::config]') } + it { is_expected.to contain_class('mongodb::server::config'). + that_notifies('Class[mongodb::server::service]') } + it { is_expected.to contain_class('mongodb::server::service') } + + it { + is_expected.to contain_mongodb__db('admin').with({ + 'user' => 'admin', + 'password' => 'password', + 'roles' => ["userAdmin", "readWrite", "dbAdmin", "dbAdminAnyDatabase", + "readAnyDatabase", "readWriteAnyDatabase", "userAdminAnyDatabase", + "clusterAdmin", "clusterManager", "clusterMonitor", "hostManager", + "root", "restore"] + }).that_requires('Anchor[mongodb::server::end]') + } end context 'when deploying on Solaris' do @@ -57,4 +88,74 @@ end end end + + context 'when setting up replicasets' do + context 'should fail if providing both replica_sets and replset_members' do + let(:params) do + { + :replset => 'rsTest', + :replset_members => [ + 'mongo1:27017', + 'mongo2:27017', + 'mongo3:27017' + ], + :replica_sets => {} + } + end + + it { expect { is_expected.to raise_error(/Puppet::Error: You can provide either replset_members or replica_sets, not both/) } } + end + + context 'should setup using replica_sets hash' do + let(:rsConf) do + { + 'rsTest' => { + 'members' => [ + 'mongo1:27017', + 'mongo2:27017', + 'mongo3:27017', + ], + 'arbiter' => 'mongo3:27017' + } + } + end + + let(:params) do + { + :replset => 'rsTest', + :replset_config => rsConf + } + end + + it { is_expected.to contain_class('mongodb::replset').with_sets(rsConf) } + end + + context 'should setup using replset_members' do + let(:rsConf) do + { + 'rsTest' => { + 'ensure' => 'present', + 'members' => [ + 'mongo1:27017', + 'mongo2:27017', + 'mongo3:27017' + ] + } + } + end + + let(:params) do + { + :replset => 'rsTest', + :replset_members => [ + 'mongo1:27017', + 'mongo2:27017', + 'mongo3:27017' + ] + } + end + + it { is_expected.to contain_class('mongodb::replset').with_sets(rsConf) } + end + end end diff --git a/spec/defines/db_spec.rb b/spec/defines/db_spec.rb index 24b9f8d7f..4d1746b5c 100644 --- a/spec/defines/db_spec.rb +++ b/spec/defines/db_spec.rb @@ -10,8 +10,7 @@ } it 'should contain mongodb_database with mongodb::server requirement' do - is_expected.to contain_mongodb_database('testdb')\ - .with_require('Class[Mongodb::Server]') + is_expected.to contain_mongodb_database('testdb') end it 'should contain mongodb_user with mongodb_database requirement' do diff --git a/spec/unit/puppet/provider/mongodb_database/mongodb_spec.rb b/spec/unit/puppet/provider/mongodb_database/mongodb_spec.rb index ff06b7ff9..e8cfe9c8e 100644 --- a/spec/unit/puppet/provider/mongodb_database/mongodb_spec.rb +++ b/spec/unit/puppet/provider/mongodb_database/mongodb_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'tempfile' describe Puppet::Type.type(:mongodb_database).provider(:mongodb) do @@ -32,7 +33,11 @@ let(:provider) { resource.provider } before :each do + tmp = Tempfile.new('test') + @mongodconffile = tmp.path + allow(provider.class).to receive(:get_mongod_conf_file).and_return(@mongodconffile) provider.class.stubs(:mongo_eval).with('printjson(db.getMongo().getDBs())').returns(raw_dbs) + allow(provider.class).to receive(:db_ismaster).and_return(true) end let(:instance) { provider.class.instances.first } diff --git a/spec/unit/puppet/provider/mongodb_replset/mongodb_spec.rb b/spec/unit/puppet/provider/mongodb_replset/mongodb_spec.rb index 5c3387a88..b52c1249b 100644 --- a/spec/unit/puppet/provider/mongodb_replset/mongodb_spec.rb +++ b/spec/unit/puppet/provider/mongodb_replset/mongodb_spec.rb @@ -22,13 +22,29 @@ let(:provider) { described_class.new(resource) } describe '#create' do + before :each do + tmp = Tempfile.new('test') + @mongodconffile = tmp.path + allow(provider.class).to receive(:get_mongod_conf_file).and_return(@mongodconffile) + allow(provider.class).to receive(:mongo).and_return(< "Config now saved locally. Should come online in about a minute.", "ok" => 1, }) + allow(provider).to receive(:db_ismaster).and_return('{"ismaster" : true}') provider.create provider.flush end @@ -36,11 +52,13 @@ it 'should create a replicaset with arbiter' do allow(provider.class).to receive(:get_replset_properties) allow(provider).to receive(:alive_members).and_return(valid_members) + allow(provider).to receive(:master_host).and_return(false) allow(provider).to receive(:rs_arbiter).and_return('mongo3:27017') expect(provider).to receive('rs_initiate').with("{ _id: \"rs_test\", members: [ { _id: 0, host: \"mongo1:27017\" },{ _id: 1, host: \"mongo2:27017\" },{ _id: 2, host: \"mongo3:27017\", arbiterOnly: \"true\" } ] }", "mongo1:27017").and_return({ "info" => "Config now saved locally. Should come online in about a minute.", "ok" => 1, }) + allow(provider).to receive(:db_ismaster).and_return('{"ismaster" : true}') provider.create provider.flush end @@ -52,9 +70,10 @@ @mongodconffile = tmp.path allow(provider.class).to receive(:get_mongod_conf_file).and_return(@mongodconffile) end + describe 'when the replicaset does not exist' do it 'returns false' do - allow(provider.class).to receive(:mongo).and_return(< "rs_test" }) + allow(provider).to receive(:rs_status).and_return({ 'set' => 'rs_test' }) expect(provider).to receive('rs_add').twice.and_return({ 'ok' => 1 }) provider.members=(valid_members) provider.flush diff --git a/spec/unit/puppet/provider/mongodb_user/mongodb_spec.rb b/spec/unit/puppet/provider/mongodb_user/mongodb_spec.rb index 8b02a8731..841b3d61c 100644 --- a/spec/unit/puppet/provider/mongodb_user/mongodb_spec.rb +++ b/spec/unit/puppet/provider/mongodb_user/mongodb_spec.rb @@ -1,5 +1,6 @@ require 'spec_helper' require 'json' +require 'tempfile' describe Puppet::Type.type(:mongodb_user).provider(:mongodb) do @@ -24,8 +25,12 @@ let(:provider) { resource.provider } before :each do - provider.class.stubs(:mongo_eval).with('printjson(db.system.users.find().toArray())').returns(raw_users) - provider.class.stubs(:mongo_version).returns('2.6.x') + tmp = Tempfile.new('test') + @mongodconffile = tmp.path + allow(provider.class).to receive(:get_mongod_conf_file).and_return(@mongodconffile) + provider.class.stubs(:mongo_eval).with('printjson(db.system.users.find().toArray())').returns(raw_users) + provider.class.stubs(:mongo_version).returns('2.6.x') + allow(provider.class).to receive(:db_ismaster).and_return(true) end let(:instance) { provider.class.instances.first } @@ -37,6 +42,14 @@ end end + describe 'empty self.instances from slave' do + it 'doesn`t retrun array of users' do + allow(provider.class).to receive(:db_ismaster).and_return(false) + expect(provider.class.instances).to match_array([]) + end + end + + describe 'create' do it 'creates a user' do cmd_json=<<-EOS.gsub(/^\s*/, '').gsub(/$\n/, '') diff --git a/templates/mongorc.js.erb b/templates/mongorc.js.erb new file mode 100644 index 000000000..d9b46c39b --- /dev/null +++ b/templates/mongorc.js.erb @@ -0,0 +1,27 @@ +function authRequired() { + try { + if (db.serverCmdLineOpts().code == 13) { + return true; + } + return false; + } + catch (err) { + return false; + } +} + +if (authRequired()) { + try { +<% if @replset -%> + rs.slaveOk() +<% end -%> + var prev_db = db + db = db.getSiblingDB('admin') + db.auth('<%= @admin_username %>', '<%= @admin_password %>') + db = db.getSiblingDB(prev_db) + } + catch (err) { + // This isn't catching authentication errors as I'd expect... + return; + } +}