Skip to content

Commit

Permalink
Rewrite Part 2 (#140)
Browse files Browse the repository at this point in the history
* Update user_management recipe testing
* Add support for mongo gem 2.x for user add
* Cleanup resource cloning
* Suppress chef_gem compile_time warnings
* Remove Makefile
* Remove python dep since it is no longer used
* Add support for mongo gem 2.x for user delete
* Add actions as option in users array [@jrstarke](https://github.com/jrstarke)
#91
* Restrict user kitchen testing to one OS
  • Loading branch information
shortdudey123 authored and damacus committed Apr 12, 2017
1 parent 180452c commit 57b1ee5
Show file tree
Hide file tree
Showing 14 changed files with 325 additions and 33 deletions.
64 changes: 58 additions & 6 deletions .kitchen.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
---
driver:
name: vagrant
chef_version: latest
chef_version: 12.19.36
# chef_version: latest
linked_clone: true

provisioner:
require_chef_omnibus: 12.19.36

verifier:
name: inspec

Expand Down Expand Up @@ -88,10 +92,58 @@ suites:
- "recipe[sc-mongodb::user_management]"
attributes:
mongodb:
install_method: mongodb-org
# Needed to read the correct config file
# since mongo 2.6
default_init_name: mongod
dbconfig_file: mongodb.conf
config:
auth: true
users:
- username: kitchen
password: blah123
roles:
- read
database: admin
includes:
# Only need to test this on one OS since this is
# purely to test mongo ruby driver code
- centos-7.3

- name: user_management_v2
run_list:
- recipe[sc-mongodb::default]
- recipe[sc-mongodb::user_management]
attributes:
mongodb:
config:
auth: true
ruby_gems:
mongo: ~> 2.0
users:
- username: kitchen
password: blah123
roles:
- read
database: admin
includes:
# Only need to test this on one OS since this is
# purely to test mongo ruby driver code
- centos-7.3

- name: user_management_v2_delete
run_list:
- recipe[sc-mongodb::default]
- recipe[sc-mongodb::user_management]
- recipe[mongodb_spec::user_delete]
attributes:
mongodb:
config:
auth: true
ruby_gems:
mongo: ~> 2.0
users:
- username: kitchen
password: blah123
roles:
- read
database: admin
includes:
# Only need to test this on one OS since this is
# purely to test mongo ruby driver code
- centos-7.3
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ install: echo "skip bundle install"
branches:
only:
- master
- rewrite_v1

# Ensure we make ChefDK's Ruby the default
before_script:
Expand Down
2 changes: 2 additions & 0 deletions Berksfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
source 'https://supermarket.chef.io'

metadata

cookbook 'mongodb_spec', path: 'test/fixtures/cookbooks/mongodb_spec'
20 changes: 0 additions & 20 deletions Makefile

This file was deleted.

1 change: 0 additions & 1 deletion metadata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

depends 'apt', '>= 1.8.2'
depends 'yum', '>= 3.0'
depends 'python'
depends 'build-essential', '>= 5.0.0'

%w(
Expand Down
187 changes: 184 additions & 3 deletions providers/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ def user_exists?(username, connection)
connection['admin']['system.users'].find(user: username).count > 0
end

def user_exists_v2?(username, connection)
connection['system.users'].find(user: username).count > 0
end

def add_user(username, password, database, roles = [])
require 'rubygems'
require 'mongo'
Expand Down Expand Up @@ -78,6 +82,97 @@ def add_user(username, password, database, roles = [])
end
end

def add_user_v2(username, password, database, roles = [])
# Check if user is admin / admin, and warn that this should
# be overridden to unique values
if username == 'admin' && password == 'admin'
Chef::Log.warn('Default username / password detected for admin user')
Chef::Log.warn('These should be overridden to different, unique values')
end

# If authentication is required on database
# must authenticate as a userAdmin after an admin user has been created
# this will fail on the first attempt, but user will still be created
# because of the localhost exception
if (@new_resource.connection['config']['auth'] == true) || (@new_resource.connection['mongos_create_admin'] == true)
begin
connection = retrieve_db_v2(
@new_resource.connection['authentication']['username'],
@new_resource.connection['authentication']['password']
)
rescue Mongo::Auth::Unauthorized => e
# invalid creds
Chef::Log.warn("Unable to authenticate as admin user. If this is a fresh install, ignore warning: #{e}")
connection = retrieve_db_v2
rescue Mongo::Error::NoServerAvailable => e
# Replicaset not initialized
Chef::Log.warn("Server appears to be part of an uninitialized or initializing replicaset: #{e}")
Chef::Log.warn('Retrying 1 time')
sleep(@new_resource.connection['mongod_create_user']['delay'])
begin
connection = retrieve_db_v2
rescue Mongo::Error::NoServerAvailable => e
Chef::Application.fatal!("Unable to connect to mongo: #{e}")
end
end
end

admin = connection.use('admin')
db = connection.use(database)

begin
if user_exists_v2?(username, connection)
Chef::Log.warn("#{username} already exists on #{database}")
else
# Create the user
db.database.users.create(
username,
password: password,
roles: roles
)
Chef::Log.info("Created user #{username} on #{database}")
end
rescue Mongo::Error::OperationFailure => e
# User probably already exists
Chef::Application.fatal!("Unable to add user on initial try: #{e}")
rescue Mongo::Error::NoServerAvailable => e
if @new_resource.connection['is_replicaset']
# Node is part of a replicaset and may not be initialized yet, going to retry if set to
i = 0
while i < @new_resource.connection['mongod_create_user']['retries']
begin
rs_info = admin.command(replSetGetStatus: 1)
rs_info_self = rs_info.documents[0]['members'].select { |a| a['self'] }.first
has_info_message = rs_info_self.key?('infoMessage')

if rs_info_self['state'] == 1
# This node is a primary node, try to add the user
db.database.users.create(
username,
password: password,
roles: roles
)
Chef::Log.info("Created or updated user #{username} on #{database} of primary replicaset node")
break
elsif rs_info_self['state'] == 2 && has_info_message
# This node is secondary but may be in the process of an election, retry
Chef::Log.info("Unable to add user to secondary, election may be in progress, retrying in #{@new_resource.connection['mongod_create_user']['delay']} seconds...")
elsif rs_info_self['state'] == 2 && !has_info_message
# This node is secondary and not in the process of an election, bail out
Chef::Log.info('Current node appears to be a secondary node in replicaset, could not detect election in progress, not adding user')
break
end
end

i += 1
sleep(@new_resource.connection['mongod_create_user']['delay'])
end
else
Chef::Application.fatal!("Unable to add user: #{e}")
end
end
end

# Drop a user from the database specified
def delete_user(username, database)
require 'rubygems'
Expand All @@ -104,6 +199,40 @@ def delete_user(username, database)
end
end

def delete_user_v2(username, database)
if (@new_resource.connection['config']['auth'] == true) || (@new_resource.connection['mongos_create_admin'] == true)
begin
connection = retrieve_db_v2(
@new_resource.connection['authentication']['username'],
@new_resource.connection['authentication']['password']
)
rescue Mongo::Auth::Unauthorized => e
# invalid creds
Chef::Application.fatal!("Unable to authenticate as admin user: #{e}")
connection = retrieve_db_v2
rescue Mongo::Error::NoServerAvailable => e
# Replicaset not initialized
Chef::Log.warn("Server appears to be part of an uninitialized or initializing replicaset: #{e}")
Chef::Log.warn('Retrying 1 time')
sleep(@new_resource.connection['mongod_create_user']['delay'])
begin
connection = retrieve_db_v2
rescue Mongo::Error::NoServerAvailable => e
Chef::Application.fatal!("Unable to connect to mongo: #{e}")
end
end
end

db = connection.use(database)

if user_exists_v2?(username, connection)
db.database.users.remove(username)
Chef::Log.info("Deleted user #{username} on #{database}")
else
Chef::Log.warn("Unable to delete non-existent user #{username} on #{database}")
end
end

# Get the MongoClient connection
def retrieve_db(attempt = 0)
require 'rubygems'
Expand All @@ -125,14 +254,66 @@ def retrieve_db(attempt = 0)
end
end

def retrieve_db_v2(username = nil, password = nil, attempt = 0)
require 'mongo'

host = @new_resource.connection['host'] || 'localhost'
port = @new_resource.connection['port'] || 27017

begin
Chef::Log.info("Connecting to #{host}:#{port} with #{username}")
client = Mongo::Client.new(
["#{host}:#{port}"],
user: username,
password: password,
connect_timeout: 5,
socket_timeout: 5,
max_read_retries: 5,
server_selection_timeout: 3
)

# Query the server for all database names to verify server connection
client.database_names
rescue Mongo::Error::NoServerAvailable, Mongo::Error::OperationFailure => e
if attempt < @new_resource.connection['user_management']['connection']['retries']
Chef::Log.warn("Unable to connect to MongoDB instance: #{e}, retrying in #{@new_resource.connection['user_management']['connection']['delay']} second(s)...")
sleep(@new_resource.connection['user_management']['connection']['delay'])
retrieve_db_v2(username, password, attempt + 1)
end
end

client
end

action :add do
add_user(new_resource.username, new_resource.password, new_resource.database, new_resource.roles)
require 'mongo'
if defined?(Mongo::VERSION) && Gem::Version.new(Mongo::VERSION) >= Gem::Version.new('2.0.0')
# The gem displays a lot of debug messages by default so set to INFO
Mongo::Logger.logger.level = ::Logger::INFO
add_user_v2(new_resource.username, new_resource.password, new_resource.database, new_resource.roles)
else # mongo gem version 1.x
add_user(new_resource.username, new_resource.password, new_resource.database, new_resource.roles)
end
end

action :delete do
delete_user(new_resource.username, new_resource.database)
require 'mongo'
if defined?(Mongo::VERSION) && Gem::Version.new(Mongo::VERSION) >= Gem::Version.new('2.0.0')
# The gem displays a lot of debug messages by default so set to INFO
Mongo::Logger.logger.level = ::Logger::INFO
delete_user_v2(new_resource.username, new_resource.database)
else # mongo gem version 1.x
delete_user(new_resource.username, new_resource.database)
end
end

action :modify do
add_user(new_resource.username, new_resource.password, new_resource.database, new_resource.roles)
require 'mongo'
if defined?(Mongo::VERSION) && Gem::Version.new(Mongo::VERSION) >= Gem::Version.new('2.0.0')
# The gem displays a lot of debug messages by default so set to INFO
Mongo::Logger.logger.level = ::Logger::INFO
# TODO: implement modify for 2.x gem
else # mongo gem version 1.x
add_user(new_resource.username, new_resource.password, new_resource.database, new_resource.roles)
end
end
6 changes: 4 additions & 2 deletions recipes/install.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
end

# just-in-case config file drop
template node['mongodb']['dbconfig_file'][config_type] do
template "#{node['mongodb']['dbconfig_file'][config_type]} install" do
path node['mongodb']['dbconfig_file'][config_type]
cookbook node['mongodb']['template_cookbook']
source node['mongodb']['dbconfig_file']['template']
group node['mongodb']['root_group']
Expand Down Expand Up @@ -68,7 +69,8 @@
action :nothing
end

template init_file do
template "#{init_file} install" do
path init_file
cookbook node['mongodb']['template_cookbook']
source node['mongodb']['init_script_template']
group node['mongodb']['root_group']
Expand Down
1 change: 1 addition & 0 deletions recipes/mongo_gem.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,6 @@
node['mongodb']['ruby_gems'].each do |gem, version|
chef_gem gem do
version version
compile_time false
end
end
2 changes: 2 additions & 0 deletions recipes/user_management.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
action :nothing
subscribes :add, 'ruby_block[config_replicaset]', :delayed
subscribes :add, 'ruby_block[config_sharding]', :delayed
else
action user['action'] || :add
end
end
end
3 changes: 3 additions & 0 deletions test/fixtures/cookbooks/mongodb_spec/metadata.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name 'mongodb_spec'

depends 'sc-mongodb'
6 changes: 6 additions & 0 deletions test/fixtures/cookbooks/mongodb_spec/recipes/user_delete.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
mongodb_user '"kitchen" user delete' do
username 'kitchen'
database 'admin'
connection node['mongodb']
action :delete
end
Loading

0 comments on commit 57b1ee5

Please sign in to comment.