Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite Part 2 #140

Merged
merged 10 commits into from
Apr 12, 2017
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

This comment was marked as outdated.

This comment was marked as outdated.

This comment was marked as outdated.


# 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