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

Move get_file and save_file to ConfigurationManagementMixin #17494

Merged
merged 1 commit into from
May 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions app/models/miq_server/configuration_management.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,28 @@ def get_config(type = "vmdb")
def set_config(config)
config = config.config if config.respond_to?(:config)
add_settings_for_resource(config)
ntp_reload_queue
end

def reload_settings
Vmdb::Settings.reload! if is_local?
return if is_remote?

Vmdb::Settings.reload!
activate_settings_for_appliance
end

# The purpose of this method is to do special activation of things
# that can only happen once per server. Normally, the
# VMDB::Config::Activator would be used, however the activations
# will end up occurring once per worker on the entire server, which
# can be detrimental.
#
# As an example, ntp_reload works by telling systemctl to restart
# chronyd. However, if this occurs on every worker, you end up with
# dozens of calls to `systemctl restart chronyd` simultaneously.
# Instead, this method will allow it to only happen once on
# the reload of settings in evmserverd.
private def activate_settings_for_appliance
ntp_reload_queue
end

def servers_for_settings_reload
Expand Down
9 changes: 9 additions & 0 deletions app/models/mixins/configuration_management_mixin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,20 @@ def settings_for_resource
Vmdb::Settings.for_resource(self)
end

def settings_for_resource_yaml
Vmdb::Settings.for_resource_yaml(self)
end

def add_settings_for_resource(settings)
Vmdb::Settings.save!(self, settings)
immediately_reload_settings
end

def add_settings_for_resource_yaml(contents)
Vmdb::Settings.save_yaml!(self, contents)
immediately_reload_settings
end

def remove_settings_path_for_resource(*keys)
Vmdb::Settings.destroy!(self, keys)
immediately_reload_settings
Expand Down
17 changes: 5 additions & 12 deletions lib/vmdb/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,15 @@ def save(resource = MiqServer.my_server)

# NOTE: Used by Configuration -> Advanced
def self.get_file(resource = MiqServer.my_server)
Vmdb::Settings.encrypt_passwords!(resource.settings_for_resource.to_hash).to_yaml
resource.settings_for_resource_yaml
end

# NOTE: Used by Configuration -> Advanced
def self.save_file(contents, resource = MiqServer.my_server)
config = new("vmdb")

begin
config.config = Vmdb::Settings.decrypt_passwords!(YAML.load(contents))
config.validate
rescue StandardError, Psych::SyntaxError => err
config.errors = [[:contents, "File contents are malformed, '#{err.message}'"]]
end

return config.errors unless config.errors.blank?
config.save(resource)
resource.add_settings_for_resource_yaml(contents)
rescue Vmdb::Settings::ConfigurationInvalid => err
err.errors
else
true
end

Expand Down
32 changes: 27 additions & 5 deletions lib/vmdb/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@ module Vmdb
class Settings
extend Vmdb::SettingsWalker::ClassMethods

class ConfigurationInvalid < StandardError; end
class ConfigurationInvalid < StandardError
attr_accessor :errors

def initialize(errors)
@errors = errors
message = errors.map { |k, v| "#{k}: #{v}" }.join("; ")
super(message)
end
end

PASSWORD_FIELDS = Vmdb::SettingsWalker::PASSWORD_FIELDS
DUMP_LOG_FILE = Rails.root.join("log/last_settings.txt").freeze
Expand Down Expand Up @@ -56,10 +64,7 @@ def self.save!(resource, hash)
new_settings = build_without_local(resource).load!.merge!(hash).to_hash

valid, errors = validate(new_settings)
unless valid
message = errors.map { |k, v| "#{k}: #{v}" }.join("; ")
raise ConfigurationInvalid, message
end
raise ConfigurationInvalid.new(errors) unless valid # rubocop:disable Style/RaiseArgs

hash_for_parent = parent_settings_without_local(resource).load!.to_hash
diff = HashDiffer.diff(hash_for_parent, new_settings)
Expand All @@ -68,6 +73,18 @@ def self.save!(resource, hash)
apply_settings_changes(resource, deltas)
end

def self.save_yaml!(resource, contents)
require 'yaml'
hash =
begin
decrypt_passwords!(YAML.load(contents))
rescue => err
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you look at the previous code, this enumerated both StandardError and Psych::SyntaxError. We had done this in the past because Psych::SyntaxError used to not derive from StandardError, but in Ruby 2.3 and 2.4 that is no longer the case, so we can just catch StandardError like normal. I did this because rubocop rightly complains with Lint/ShadowedException

[1] pry(main)> Psych::SyntaxError < StandardError
=> true
[2] pry(main)> RUBY_VERSION
=> "2.3.3"
[3] pry(main)> Psych::VERSION
=> "2.1.0"

raise ConfigurationInvalid.new(:contents => "File contents are malformed: #{err.message.inspect}")
end

save!(resource, hash)
end

def self.destroy!(resource, keys)
return if keys.blank?
settings_path = File.join("/", keys.collect(&:to_s))
Expand All @@ -78,6 +95,11 @@ def self.for_resource(resource)
build(resource).load!
end

def self.for_resource_yaml(resource)
require 'yaml'
encrypt_passwords!(for_resource(resource).to_hash).to_yaml
end

def self.template_settings
build_template.load!
end
Expand Down
5 changes: 3 additions & 2 deletions spec/lib/vmdb/config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@
end

context ".save_file" do
let!(:resource) { FactoryGirl.create(:miq_server) }

it "normal" do
resource = FactoryGirl.create(:miq_server)
MiqRegion.seed
data = {}
data.store_path(:api, :token_ttl, "1.day")
Expand All @@ -62,7 +63,7 @@
end

it "catches syntax errors" do
errors = VMDB::Config.save_file("xxx")
errors = VMDB::Config.save_file("xxx", resource)
expect(errors.length).to eq(1)
expect(errors.first[0]).to eq(:contents)
expect(errors.first[1]).to start_with("File contents are malformed")
Expand Down
83 changes: 83 additions & 0 deletions spec/lib/vmdb/settings_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,62 @@
end
end

describe "save_yaml!" do
let(:miq_server) { FactoryGirl.create(:miq_server) }

it "saves the settings" do
data = {:api => {:token_ttl => "1.day"}}.to_yaml
described_class.save_yaml!(miq_server, data)

miq_server.reload

expect(miq_server.settings_changes.count).to eq 1
expect(miq_server.settings_changes.first).to have_attributes(:key => "/api/token_ttl",
:value => "1.day")
end

it "handles incoming unencrypted values" do
password = "pa$$word"
encrypted = MiqPassword.encrypt(password)

data = {:authentication => {:bind_pwd => password}}.to_yaml
described_class.save_yaml!(miq_server, data)

miq_server.reload

expect(miq_server.settings_changes.count).to eq 1
expect(miq_server.settings_changes.first).to have_attributes(:key => "/authentication/bind_pwd",
:value => encrypted)
end

it "handles incoming encrypted values" do
password = "pa$$word"
encrypted = MiqPassword.encrypt(password)

data = {:authentication => {:bind_pwd => encrypted}}.to_yaml
described_class.save_yaml!(miq_server, data)

miq_server.reload

expect(miq_server.settings_changes.count).to eq 1
expect(miq_server.settings_changes.first).to have_attributes(:key => "/authentication/bind_pwd",
:value => encrypted)
end

{
"syntax" => "--- -", # invalid YAML
"non-syntax" => "xxx" # valid YAML, but invalid config
}.each do |type, contents|
it "catches #{type} errors" do
expect { described_class.save_yaml!(miq_server, contents) }.to raise_error(described_class::ConfigurationInvalid) do |err|
expect(err.errors.size).to eq 1
expect(err.errors[:contents]).to start_with("File contents are malformed")
expect(err.message).to include("contents: File contents are malformed")
end
end
end
end

shared_examples_for "password handling" do
subject do
described_class.send(method, Settings)
Expand Down Expand Up @@ -397,6 +453,33 @@
end
end

describe ".for_resource_yaml" do
it "fetches the yaml with changes" do
miq_server = FactoryGirl.create(:miq_server)
described_class.save!(miq_server, :api => {:token_ttl => "1.day"})

yaml = described_class.for_resource_yaml(miq_server)
expect(yaml).to_not include("Config::Options")

hash = YAML.load(yaml)
expect(hash).to be_kind_of Hash
expect(hash.fetch_path(:api, :token_ttl)).to eq "1.day"
end

it "ensures passwords are encrypted" do
password = "pa$$word"
encrypted = MiqPassword.encrypt(password)

miq_server = FactoryGirl.create(:miq_server)
described_class.save!(miq_server, :authentication => {:bind_pwd => password})

yaml = described_class.for_resource_yaml(miq_server)

hash = YAML.load(yaml)
expect(hash.fetch_path(:authentication, :bind_pwd)).to eq encrypted
end
end

it "with .local file" do
stub_local_settings_file(
Rails.root.join("config/settings/test.local.yml"),
Expand Down