diff --git a/lib/facter/is_master.rb b/lib/facter/is_master.rb index 2ac6e36d6..ab6872c73 100644 --- a/lib/facter/is_master.rb +++ b/lib/facter/is_master.rb @@ -62,7 +62,7 @@ def get_options_from_config(file) e = File.exist?('/root/.mongorc.js') ? 'load(\'/root/.mongorc.js\'); ' : '' # Check if the mongodb server is responding: - Facter::Core::Execution.exec("mongo --quiet #{options} --eval \"#{e}printjson(db.adminCommand({ ping: 1 }))\"") + Facter::Core::Execution.exec("mongo --quiet #{options} --eval \"#{e}JSON.stringify(db.adminCommand({ ping: 1 }))\"") if $CHILD_STATUS.success? Facter::Core::Execution.exec("mongo --quiet #{options} --eval \"#{e}db.isMaster().ismaster\"") diff --git a/lib/puppet/provider/mongodb_database/mongodb.rb b/lib/puppet/provider/mongodb_database/mongodb.rb index b306b9395..c03e91b53 100644 --- a/lib/puppet/provider/mongodb_database/mongodb.rb +++ b/lib/puppet/provider/mongodb_database/mongodb.rb @@ -8,7 +8,7 @@ def self.instances require 'json' pre_cmd = 'try { rs.secondaryOk() } catch (err) { rs.slaveOk() }' - dbs = JSON.parse mongo_eval(pre_cmd + ';printjson(db.getMongo().getDBs())') + dbs = JSON.parse mongo_eval(pre_cmd + ';JSON.stringify(db.getMongo().getDBs())') dbs['databases'].map do |db| new(name: db['name'], diff --git a/lib/puppet/provider/mongodb_replset/mongo.rb b/lib/puppet/provider/mongodb_replset/mongo.rb index da5bc9e71..0fb1bff57 100644 --- a/lib/puppet/provider/mongodb_replset/mongo.rb +++ b/lib/puppet/provider/mongodb_replset/mongo.rb @@ -157,7 +157,7 @@ def get_hosts_status(members) raise Puppet::Error, "Can't configure replicaset #{name}, host #{host} is not supposed to be part of a replicaset." end - if auth_enabled && status.key?('errmsg') && (status['errmsg'].include?('unauthorized') || status['errmsg'].include?('not authorized')) + if auth_enabled && status.key?('errmsg') && (status['errmsg'].include?('unauthorized') || status['errmsg'].include?('not authorized') || status['codeName'] == 'Unauthorized') Puppet.warning "Host #{host} is available, but you are unauthorized because of authentication is enabled: #{auth_enabled}" alive.push(member) end @@ -388,7 +388,7 @@ def mongo_command(command, host, retries = 4) def self.mongo_command(command, host = nil, retries = 4) begin - output = mongo_eval("printjson(#{command})", 'admin', retries, host) + output = mongo_eval("JSON.stringify(#{command})", 'admin', retries, host) rescue Puppet::ExecutionFailure => e Puppet.debug "Got an exception: #{e}" raise diff --git a/lib/puppet/provider/mongodb_shard/mongo.rb b/lib/puppet/provider/mongodb_shard/mongo.rb index 15d7e155e..609165c29 100644 --- a/lib/puppet/provider/mongodb_shard/mongo.rb +++ b/lib/puppet/provider/mongodb_shard/mongo.rb @@ -147,7 +147,7 @@ def self.mongo_command(command, host = nil, _retries = 4) args = [] args << '--quiet' args << ['--host', host] if host - args << ['--eval', "printjson(#{command})"] + args << ['--eval', "JSON.stringify(#{command})"] output = mongo(args.flatten) rescue Puppet::ExecutionFailure => e raise unless e =~ %r{Error: couldn't connect to server} && wait <= (2**max_wait) diff --git a/lib/puppet/provider/mongodb_user/mongodb.rb b/lib/puppet/provider/mongodb_user/mongodb.rb index 0b424e2cd..ad45f0795 100644 --- a/lib/puppet/provider/mongodb_user/mongodb.rb +++ b/lib/puppet/provider/mongodb_user/mongodb.rb @@ -8,7 +8,7 @@ def self.instances require 'json' if db_ismaster - users = JSON.parse mongo_eval('printjson(db.system.users.find().toArray())') + users = JSON.parse mongo_eval('JSON.stringify(db.system.users.find().toArray())') users.map do |user| new(name: user['_id'], diff --git a/lib/puppet/util/mongodb_output.rb b/lib/puppet/util/mongodb_output.rb index da5ddbb68..fb9bc50e8 100644 --- a/lib/puppet/util/mongodb_output.rb +++ b/lib/puppet/util/mongodb_output.rb @@ -1,16 +1,41 @@ +require 'json' + module Puppet module Util module MongodbOutput def self.sanitize(data) + # If it already happily contains a valid json, do not do any sanitization + return data if is_parseable_json(data) + # Dirty hack to remove JavaScript objects data.gsub!(%r{\w+\((\d+).+?\)}, '\1') # Remove extra parameters from 'Timestamp(1462971623, 1)' Objects data.gsub!(%r{\w+\((.+?)\)}, '\1') + # Probably theres a json object that we could extract from the output + maybe_json = try_extract_json(data) + return maybe_json unless maybe_json.nil? + data.gsub!(%r{^Error\:.+}, '') data.gsub!(%r{^.*warning\:.+}, '') # remove warnings if sslAllowInvalidHostnames is true data.gsub!(%r{^.*The server certificate does not match the host name.+}, '') # remove warnings if sslAllowInvalidHostnames is true mongo 3.x data end + + def self.is_parseable_json(data) + !!JSON.parse(data) + rescue JSON::ParserError + false + end + + def self.try_extract_json(data) + json_data = data.dup + unescaped_quotes = json_data.scan(%r{:\s*"(.*"+.*)"}).flatten + for str in unescaped_quotes do + json_data.sub!(str, str.gsub('"', '\"')) + end + maybe_json = json_data.gsub(%r{^[^{]*(?{[\P{Cn}\P{Cs}]*})[^}]*$}, '\k') + maybe_json if is_parseable_json(maybe_json) + end end end end diff --git a/spec/acceptance/database_spec.rb b/spec/acceptance/database_spec.rb index 269a8f8a4..6824c11e3 100644 --- a/spec/acceptance/database_spec.rb +++ b/spec/acceptance/database_spec.rb @@ -20,8 +20,8 @@ class { 'mongodb::server': } apply_manifest(pp, catch_changes: true) end it 'creates the databases' do - shell("mongo testdb1 --eval 'printjson(db.getMongo().getDBs())'") - shell("mongo testdb2 --eval 'printjson(db.getMongo().getDBs())'") + shell("mongo testdb1 --eval 'JSON.stringify(db.getMongo().getDBs())'") + shell("mongo testdb2 --eval 'JSON.stringify(db.getMongo().getDBs())'") end end @@ -46,8 +46,8 @@ class { 'mongodb::server': apply_manifest(pp, catch_changes: true) end it 'creates the database' do - shell("mongo testdb1 --port 27018 --eval 'printjson(db.getMongo().getDBs())'") - shell("mongo testdb2 --port 27018 --eval 'printjson(db.getMongo().getDBs())'") + shell("mongo testdb1 --port 27018 --eval 'JSON.stringify(db.getMongo().getDBs())'") + shell("mongo testdb2 --port 27018 --eval 'JSON.stringify(db.getMongo().getDBs())'") end end end diff --git a/spec/acceptance/replset_spec.rb b/spec/acceptance/replset_spec.rb index f47b218f4..613eacac5 100644 --- a/spec/acceptance/replset_spec.rb +++ b/spec/acceptance/replset_spec.rb @@ -45,7 +45,7 @@ class { 'mongodb::client': } } EOS apply_manifest_on(hosts_as('master'), pp, catch_failures: true) - on(hosts_as('master'), 'mongo --quiet --eval "printjson(rs.conf())"') do |r| + on(hosts_as('master'), 'mongo --quiet --eval "JSON.stringify(rs.conf())"') do |r| expect(r.stdout).to match %r{#{hosts[0]}:27017} expect(r.stdout).to match %r{#{hosts[1]}:27017} end @@ -58,14 +58,14 @@ class { 'mongodb::client': } end it 'checks the data on the master' do - on hosts_as('master'), %{mongo --verbose --eval 'printjson(db.test.findOne({name:"test1"}))'} do |r| + on hosts_as('master'), %{mongo --verbose --eval 'JSON.stringify(db.test.findOne({name:"test1"}))'} do |r| expect(r.stdout).to match %r{some value} end end it 'checks the data on the slave' do sleep(10) - on hosts_as('slave'), %{mongo --verbose --eval 'try { rs.secondaryOk() } catch (err) { rs.slaveOk() }; printjson(db.test.findOne({name:"test1"}))'} do |r| + on hosts_as('slave'), %{mongo --verbose --eval 'try { rs.secondaryOk() } catch (err) { rs.slaveOk() }; JSON.stringify(db.test.findOne({name:"test1"}))'} do |r| expect(r.stdout).to match %r{some value} end end @@ -174,7 +174,7 @@ class { 'mongodb::server': 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| + on(hosts_as('master'), 'mongo --quiet --eval "load(\'/root/.mongorc.js\');JSON.stringify(rs.conf())"') do |r| expect(r.stdout).to match %r{#{hosts[0]}:27017} expect(r.stdout).to match %r{#{hosts[1]}:27017} end @@ -187,14 +187,14 @@ class { 'mongodb::server': 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| + on hosts_as('master'), %{mongo test --verbose --eval 'load("/root/.mongorc.js");JSON.stringify(db.dummyData.findOne())'} do |r| expect(r.stdout).to match %r{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");try { rs.secondaryOk() } catch (err) { rs.slaveOk() };printjson(db.dummyData.findOne())'} do |r| + on hosts_as('slave'), %{mongo test --verbose --eval 'load("/root/.mongorc.js");try { rs.secondaryOk() } catch (err) { rs.slaveOk() };JSON.stringify(db.dummyData.findOne())'} do |r| expect(r.stdout).to match %r{created_by_puppet} end end diff --git a/spec/acceptance/server_spec.rb b/spec/acceptance/server_spec.rb index 6548a6620..0294b5f34 100644 --- a/spec/acceptance/server_spec.rb +++ b/spec/acceptance/server_spec.rb @@ -124,7 +124,7 @@ class { 'mongodb::client': } it { is_expected.to contain 'db.auth(\'admin\', \'password\')' } end - describe command("mongo admin --quiet --eval \"load('/root/.mongorc.js');printjson(db.getUser('admin')['customData'])\"") do + describe command("mongo admin --quiet --eval \"load('/root/.mongorc.js');JSON.stringify(db.getUser('admin')['customData'])\"") do its(:exit_status) { is_expected.to eq 0 } its(:stdout) { is_expected.to match "{ \"createdBy\" : \"Puppet Mongodb_user['User admin on db admin']\" }\n" } end diff --git a/spec/acceptance/sharding_spec.rb b/spec/acceptance/sharding_spec.rb index 4d9caa402..38cf575af 100644 --- a/spec/acceptance/sharding_spec.rb +++ b/spec/acceptance/sharding_spec.rb @@ -43,7 +43,7 @@ class { 'mongodb::client': } EOS apply_manifest_on(hosts_as('router'), pp, catch_failures: true) - on(hosts_as('router'), 'mongo --quiet --eval "printjson(sh.status())"') do |r| + on(hosts_as('router'), 'mongo --quiet --eval "JSON.stringify(sh.status())"') do |r| expect(r.stdout).to match %r{foo\/shard:27018} expect(r.stdout).to match %r{foo\.toto} end diff --git a/spec/unit/puppet/provider/mongodb_database/mongodb_spec.rb b/spec/unit/puppet/provider/mongodb_database/mongodb_spec.rb index 332c78263..e20789a0c 100644 --- a/spec/unit/puppet/provider/mongodb_database/mongodb_spec.rb +++ b/spec/unit/puppet/provider/mongodb_database/mongodb_spec.rb @@ -36,7 +36,7 @@ tmp = Tempfile.new('test') mongodconffile = tmp.path allow(provider.class).to receive(:mongod_conf_file).and_return(mongodconffile) - allow(provider.class).to receive(:mongo_eval).with('try { rs.secondaryOk() } catch (err) { rs.slaveOk() };printjson(db.getMongo().getDBs())').and_return(raw_dbs) + allow(provider.class).to receive(:mongo_eval).with('try { rs.secondaryOk() } catch (err) { rs.slaveOk() };JSON.stringify(db.getMongo().getDBs())').and_return(raw_dbs) allow(provider.class).to receive(:db_ismaster).and_return(true) end diff --git a/spec/unit/puppet/provider/mongodb_user/mongodb_spec.rb b/spec/unit/puppet/provider/mongodb_user/mongodb_spec.rb index 06b559043..3d192c9d4 100644 --- a/spec/unit/puppet/provider/mongodb_user/mongodb_spec.rb +++ b/spec/unit/puppet/provider/mongodb_user/mongodb_spec.rb @@ -30,7 +30,7 @@ tmp = Tempfile.new('test') mongodconffile = tmp.path allow(provider.class).to receive(:mongod_conf_file).and_return(mongodconffile) - allow(provider.class).to receive(:mongo_eval).with('printjson(db.system.users.find().toArray())').and_return(raw_users) + allow(provider.class).to receive(:mongo_eval).with('JSON.stringify(db.system.users.find().toArray())').and_return(raw_users) allow(provider.class).to receive(:mongo_version).and_return('2.6.x') allow(provider.class).to receive(:db_ismaster).and_return(true) end diff --git a/spec/unit/puppet/util/mongodb_output_spec.rb b/spec/unit/puppet/util/mongodb_output_spec.rb index 4eb222c48..94214018f 100644 --- a/spec/unit/puppet/util/mongodb_output_spec.rb +++ b/spec/unit/puppet/util/mongodb_output_spec.rb @@ -53,6 +53,45 @@ EOT end + let(:corrupted_output) do + <<-EOT + Error: Authentication failed. + 2021-05-11T15:35:19.647+0200 E QUERY [thread1] Error: Could not retrieve replica set config: { + "ok" : 0, + "errmsg" : "not authorized on admin to execute command { replSetGetConfig: 1.0, $clusterTime: { clusterTime: Timestamp(0, 0), signature: { hash: BinData(0, 0000000000000000000000000000000000000000), keyId: 0 } }, $readPreference: { mode: \"secondaryPreferred\" }, $db: \"admin\" }", + "code" : 13, + "codeName" : "Unauthorized", + "$clusterTime" : { + "clusterTime" : Timestamp(0, 0), + "signature" : { + "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), + "keyId" : NumberLong(0) + } + } + } : + rs.conf@src/mongo/shell/utils.js:1323:11 + @(shell eval):1:43' + EOT + end + + let(:corrected_corrupted_output) do + <<-EOT + { + "ok" : 0, + "errmsg" : "not authorized on admin to execute command { replSetGetConfig: 1.0, $clusterTime: { clusterTime: 0, signature: { hash: 0, keyId: 0 } }, $readPreference: { mode: \\"secondaryPreferred\\" }, $db: \\"admin\\" }", + "code" : 13, + "codeName" : "Unauthorized", + "$clusterTime" : { + "clusterTime" : 0, + "signature" : { + "hash" : 0, + "keyId" : 0 + } + } + } + EOT + end + describe '.sanitize' do it 'returns a valid json' do sanitized_json = described_class.sanitize(bson_data) @@ -62,5 +101,14 @@ sanitized_json = described_class.sanitize(bson_data) expect(JSON.parse(sanitized_json)).to include(JSON.parse(json_data)) end + + it 'extracts json from a corrupted output' do + sanitized_json = described_class.sanitize(corrupted_output) + expect(JSON.parse(sanitized_json)).to eq(JSON.parse(corrected_corrupted_output)) + end + + it 'returns string as is if no json there' do + expect(described_class.sanitize('3.6.3')).to eq('3.6.3') + end end end