diff options
author | Bryan McLellan <btm@loftninjas.org> | 2019-04-11 13:00:31 -0400 |
---|---|---|
committer | Bryan McLellan <btm@loftninjas.org> | 2019-04-15 16:52:38 -0400 |
commit | d1bc05187cf680e3149a924aa1bddff36d579700 (patch) | |
tree | 186e013423d7bd40542390fd70f213f9505f2fb2 | |
parent | 6df07f8b55d2e56288f8b1c0ff60a048ce376c74 (diff) | |
download | chef-btm/locale-fix.tar.gz |
Refactor provider out of locale resource and limit to Linuxbtm/locale-fix
Signed-off-by: Bryan McLellan <btm@loftninjas.org>
-rw-r--r-- | lib/chef/provider/locale.rb | 106 | ||||
-rw-r--r-- | lib/chef/providers.rb | 1 | ||||
-rw-r--r-- | lib/chef/resource/locale.rb | 84 | ||||
-rw-r--r-- | spec/functional/resource/locale_spec.rb | 102 | ||||
-rw-r--r-- | spec/unit/provider/locale_spec.rb | 223 | ||||
-rw-r--r-- | spec/unit/resource/locale_spec.rb | 156 |
6 files changed, 390 insertions, 282 deletions
diff --git a/lib/chef/provider/locale.rb b/lib/chef/provider/locale.rb new file mode 100644 index 0000000000..fc23ebea11 --- /dev/null +++ b/lib/chef/provider/locale.rb @@ -0,0 +1,106 @@ +# +# Copyright:: 2011-2016, Heavy Water Software Inc. +# Copyright:: 2016-2018, Chef Software Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific slanguage governing permissions and +# limitations under the License. +# + +require "chef/resource" + +class Chef + class Provider + class Locale < Chef::Provider + provides :locale + + LOCALE_CONF = "/etc/locale.conf".freeze + LOCALE_PLATFORM_FAMILIES = [ "rhel", "fedora", "amazon", "debian" ].freeze + + def load_current_resource + end + + def define_resource_requirements + requirements.assert(:all_actions) do |a| + a.assertion { LOCALE_PLATFORM_FAMILIES.include?(node[:platform_family]) } + a.failure_message(Chef::Exceptions::ProviderNotFound, "The locale resource is not supported on platform family: #{node[:platform_family]}") + end + end + + action :update do + description "Update the system's locale." + begin + unless up_to_date? + converge_by "Updating System Locale" do + generate_locales unless unavailable_locales.empty? + update_locale + end + end + end + end + + private + + # Generates the localisation files from templates using locale-gen. + # @see http://manpages.ubuntu.com/manpages/cosmic/man8/locale-gen.8.html + # @raise [Mixlib::ShellOut::ShellCommandFailed] not a supported language or locale + # + def generate_locales + shell_out!("locale-gen #{unavailable_locales.join(' ')}") + end + + # Updates system locale by appropriately writing them in /etc/locale.conf + # @note This locale change won't affect the current run. At this time it is an exercise + # left to the user to restart or reboot if the locale change is required at + # later part of the client run. + # @see https://wiki.archlinux.org/index.php/locale#Setting_the_system_locale + # + def update_locale + r = Chef::Resource::File.new(LOCALE_CONF, run_context) + r.content(new_content) + r.run_action(:create) + new_resource.updated_by_last_action(true) if r.updated? + end + + # @return [Array<String>] Locales that user wants to set but are not available on + # the system. They are required to be generated. + # + def unavailable_locales + @unavailable_locales ||= begin + available = shell_out!("locale -a").stdout.split("\n") + required = [new_resource.lang, new_resource.lc_env.values].flatten.compact.uniq + required - available + end + end + + # @return [String] Contents that are required to be + # updated in /etc/locale.conf + # + def new_content + @new_content ||= begin + content = {} + content = new_resource.lc_env.dup if new_resource.lc_env + content["LANG"] = new_resource.lang if new_resource.lang + content.sort.map { |t| t.join("=") }.join("\n") + "\n" + end + end + + # @return [Boolean] Whether any modification is required in /etc/locale.conf + # + def up_to_date? + old_content = ::File.read(LOCALE_CONF) + new_content == old_content + rescue + false # We need to create the file if it is not present + end + end + end +end diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb index 4603be7746..4a62813579 100644 --- a/lib/chef/providers.rb +++ b/lib/chef/providers.rb @@ -35,6 +35,7 @@ require "chef/provider/http_request" require "chef/provider/ifconfig" require "chef/provider/launchd" require "chef/provider/link" +require "chef/provider/locale" require "chef/provider/log" require "chef/provider/ohai" require "chef/provider/mdadm" diff --git a/lib/chef/resource/locale.rb b/lib/chef/resource/locale.rb index b16cfe7e3c..17b7463bef 100644 --- a/lib/chef/resource/locale.rb +++ b/lib/chef/resource/locale.rb @@ -24,9 +24,10 @@ class Chef description "Use the locale resource to set the system's locale." introduced "14.5" + default_action :update + allowed_actions :update LC_VARIABLES = %w{LC_ADDRESS LC_COLLATE LC_CTYPE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE LC_TIME}.freeze - LOCALE_CONF = "/etc/locale.conf".freeze LOCALE_REGEX = /\A\S+/.freeze property :lang, String, @@ -58,86 +59,7 @@ class Chef # def lc_all(arg = nil) unless arg.nil? - Chef.deprecated(:locale_lc_all, "Changing LC_ALL can break Chef's parsing of command output in unexpected ways.\n Use one of the more specific LC_ properties as needed.") - end - end - - action :update do - description "Update the system's locale." - begin - unless up_to_date? - converge_by "Updating System Locale" do - generate_locales unless unavailable_locales.empty? - update_locale - end - end - rescue - # It might affect debugging - raise "#{node['platform']} platform is not supported by the chef locale resource. " + - "If you believe this is in error please file an issue at https://github.com/chef/chef/issues" - end - end - - action_class do - - # Generates the localisation files from templates using locale-gen. - # @see http://manpages.ubuntu.com/manpages/cosmic/man8/locale-gen.8.html - # @raise [Mixlib::ShellOut::ShellCommandFailed] not a supported language or locale - # - def generate_locales - bash "Generating locales: #{unavailable_locales.join(' ')}" do - code <<~CODE - if type locale-gen >/dev/null 2>&1 - then - locale-gen #{unavailable_locales.join(' ')} - fi - CODE - end - end - - # Updates system locale by appropriately writing them in /etc/locale.conf - # @note This locale change won't affect the current run. At this time it is an exercise - # left to the user to restart or reboot if the locale change is required at - # later part of the client run. - # @see https://wiki.archlinux.org/index.php/locale#Setting_the_system_locale - # - def update_locale - file "Updating system locale" do - path LOCALE_CONF - content new_content - end - end - - # @return [Array<String>] Locales that user wants to set but are not available on - # the system. They are required to be generated. - # - def unavailable_locales - @unavailable_locales ||= begin - available = shell_out!("locale -a").stdout.split("\n") - required = [new_resource.lang, new_resource.lc_env.values].flatten.compact.uniq - required - available - end - end - - # @return [String] Contents that are required to be - # updated in /etc/locale.conf - # - def new_content - @new_content ||= begin - content = {} - content = new_resource.lc_env.dup if new_resource.lc_env - content["LANG"] = new_resource.lang if new_resource.lang - content.sort.map { |t| t.join("=") }.join("\n") + "\n" - end - end - - # @return [Boolean] Whether any modification is required in /etc/locale.conf - # - def up_to_date? - old_content = ::File.read(LOCALE_CONF) - new_content == old_content - rescue - false # We need to create the file if it is not present + Chef.deprecated(:locale_lc_all, "Changing LC_ALL can break Chef's parsing of command output in unobvious ways and is no longer supported.\nUse one of the more specific LC_ properties.") end end end diff --git a/spec/functional/resource/locale_spec.rb b/spec/functional/resource/locale_spec.rb index 61ef4630bc..dce23aea76 100644 --- a/spec/functional/resource/locale_spec.rb +++ b/spec/functional/resource/locale_spec.rb @@ -18,61 +18,73 @@ require "spec_helper" -describe Chef::Resource::Locale, :requires_root, :not_supported_on_windows do - - let(:node) { Chef::Node.new } +describe Chef::Resource::Locale, :requires_root do + let(:node) do + n = Chef::Node.new + n.consume_external_attrs(OHAI_SYSTEM.data, {}) + n + end let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:resource) { Chef::Resource::Locale.new("fakey_fakerton", run_context) } - def sets_system_locale(*locales) - system_locales = File.readlines("/etc/locale.conf") - expect(system_locales.map(&:strip)).to eq(locales) - end + context "on linux", :linux_only do + def sets_system_locale(*locales) + system_locales = File.readlines("/etc/locale.conf") + expect(system_locales.map(&:strip)).to eq(locales) + end - def unsets_system_locale(*locales) - system_locales = File.readlines("/etc/locale.conf") - expect(system_locales.map(&:strip)).not_to eq(locales) - end + def unsets_system_locale(*locales) + system_locales = File.readlines("/etc/locale.conf") + expect(system_locales.map(&:strip)).not_to eq(locales) + end - describe "action: update" do - context "Sets system variable" do - it "when LC var is given" do - resource.lc_env({ "LC_MESSAGES" => "en_US" }) - resource.run_action(:update) - sets_system_locale("LC_MESSAGES=en_US") + describe "action: update" do + context "Sets system variable" do + it "when LC var is given" do + resource.lc_env({ "LC_MESSAGES" => "en_US" }) + resource.run_action(:update) + sets_system_locale("LC_MESSAGES=en_US") + end + it "when lang is given" do + resource.lang("en_US") + resource.run_action(:update) + sets_system_locale("LANG=en_US") + end + it "when both lang & LC vars are given" do + resource.lang("en_US") + resource.lc_env({ "LC_TIME" => "en_IN" }) + resource.run_action(:update) + sets_system_locale("LANG=en_US", "LC_TIME=en_IN") + end end - it "when lang is given" do - resource.lang("en_US") - resource.run_action(:update) - sets_system_locale("LANG=en_US") - end - it "when both lang & LC vars are given" do - resource.lang("en_US") - resource.lc_env({ "LC_TIME" => "en_IN" }) - resource.run_action(:update) - sets_system_locale("LANG=en_US", "LC_TIME=en_IN") + + context "Unsets system variable" do + it "when LC var is not given" do + resource.lc_env() + resource.run_action(:update) + unsets_system_locale("LC_MESSAGES=en_US") + end + it "when lang is not given" do + resource.lang() + resource.run_action(:update) + unsets_system_locale("LANG=en_US") + end + it "when both lang & LC vars are not given" do + resource.lang() + resource.lc_env() + resource.run_action(:update) + unsets_system_locale("LANG=en_US", "LC_TIME=en_IN") + sets_system_locale("") + end end end + end - context "Unsets system variable" do - it "when LC var is not given" do - resource.lc_env() - resource.run_action(:update) - unsets_system_locale("LC_MESSAGES=en_US") - end - it "when lang is not given" do - resource.lang() - resource.run_action(:update) - unsets_system_locale("LANG=en_US") - end - it "when both lang & LC vars are not given" do - resource.lang() - resource.lc_env() - resource.run_action(:update) - unsets_system_locale("LANG=en_US", "LC_TIME=en_IN") - sets_system_locale("") - end + context "on macos", :macos_only do + it "raises an exception due to being an unsupported platform" do + resource.lang("en_US") + expect { resource.run_action(:update) }.to raise_error(Chef::Exceptions::ProviderNotFound) end end end diff --git a/spec/unit/provider/locale_spec.rb b/spec/unit/provider/locale_spec.rb new file mode 100644 index 0000000000..154846d5b0 --- /dev/null +++ b/spec/unit/provider/locale_spec.rb @@ -0,0 +1,223 @@ +# +# Author:: Nimesh Patni (<nimesh.patni@msystechnologies.com>) +# Copyright:: Copyright 2008-2018, Chef Software Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "spec_helper" + +describe Chef::Provider::Locale do + + let(:resource) { Chef::Resource::Locale.new("fakey_fakerton") } + let(:provider) { resource.provider_for_action(:update) } + + describe "default:" do + it "name would be locale" do + expect(resource.resource_name).to eq(:locale) + end + it "lang would be nil" do + expect(resource.lang).to be_nil + end + it "lc_env would be an empty hash" do + expect(resource.lc_env).to be_a(Hash) + expect(resource.lc_env).to be_empty + end + it "action would be :update" do + expect(resource.action).to eql([:update]) + end + end + + describe "validations:" do + let(:validation) { Chef::Exceptions::ValidationFailed } + context "lang" do + it "is non empty" do + expect { resource.lang("") }.to raise_error(validation) + end + it "does not contain any leading whitespaces" do + expect { resource.lang(" XX") }.to raise_error(validation) + end + end + + context "lc_env" do + it "is non empty" do + expect { resource.lc_env({ "LC_TIME" => "" }) }.to raise_error(validation) + end + it "does not contain any leading whitespaces" do + expect { resource.lc_env({ "LC_TIME" => " XX" }) }.to raise_error(validation) + end + it "keys are valid and case sensitive" do + expect { resource.lc_env({ "LC_TIMES" => " XX" }) }.to raise_error(validation) + expect { resource.lc_env({ "Lc_Time" => " XX" }) }.to raise_error(validation) + expect(resource.lc_env({ "LC_TIME" => "XX" })).to eql({ "LC_TIME" => "XX" }) + end + end + end + + describe "#unavailable_locales" do + let(:available_locales) do + <<~LOC + C + C.UTF-8 + en_AG + en_AG.utf8 + en_US + POSIX + LOC + end + before do + dummy = Mixlib::ShellOut.new + allow_any_instance_of(Chef::Mixin::ShellOut).to receive(:shell_out!).with("locale -a").and_return(dummy) + allow(dummy).to receive(:stdout).and_return(available_locales) + end + context "when all locales are available on system" do + context "with both properties" do + it "returns an empty array" do + resource.lang("en_US") + resource.lc_env({ "LC_TIME" => "en_AG.utf8", "LC_MESSAGES" => "en_AG.utf8" }) + expect(provider.send(:unavailable_locales)).to eq([]) + end + end + context "without lang" do + it "returns an empty array" do + resource.lang() + resource.lc_env({ "LC_TIME" => "en_AG.utf8", "LC_MESSAGES" => "en_AG.utf8" }) + expect(provider.send(:unavailable_locales)).to eq([]) + end + end + context "without lc_env" do + it "returns an empty array" do + resource.lang("en_US") + resource.lc_env() + expect(provider.send(:unavailable_locales)).to eq([]) + end + end + context "without both" do + it "returns an empty array" do + resource.lang() + resource.lc_env() + expect(provider.send(:unavailable_locales)).to eq([]) + end + end + end + + context "when some locales are not available" do + context "with both properties" do + it "returns list" do + resource.lang("de_DE") + resource.lc_env({ "LC_TIME" => "en_AG.utf8", "LC_MESSAGES" => "en_US.utf8" }) + expect(provider.send(:unavailable_locales)).to eq(["de_DE", "en_US.utf8"]) + end + end + context "without lang" do + it "returns list" do + resource.lang() + resource.lc_env({ "LC_TIME" => "en_AG.utf8", "LC_MESSAGES" => "en_US.utf8" }) + expect(provider.send(:unavailable_locales)).to eq(["en_US.utf8"]) + end + end + context "without lc_env" do + it "returns list" do + resource.lang("de_DE") + resource.lc_env() + expect(provider.send(:unavailable_locales)).to eq(["de_DE"]) + end + end + context "without both" do + it "returns an empty array" do + resource.lang() + resource.lc_env() + expect(provider.send(:unavailable_locales)).to eq([]) + end + end + end + end + + describe "#new_content" do + context "with both properties" do + before do + resource.lang("en_US") + resource.lc_env({ "LC_TIME" => "en_AG.utf8", "LC_MESSAGES" => "en_AG.utf8" }) + end + it "returns string" do + expect(provider.send(:new_content)).to be_a(String) + expect(provider.send(:new_content)).not_to be_empty + end + it "keys will be sorted" do + expect(provider.send(:new_content).split("\n").map { |x| x.split("=") }.collect(&:first)).to eq(%w{LANG LC_MESSAGES LC_TIME}) + end + it "ends with a new-line character" do + expect(provider.send(:new_content)[-1]).to eq("\n") + end + it "returns a valid string" do + expect(provider.send(:new_content)).to eq("LANG=en_US\nLC_MESSAGES=en_AG.utf8\nLC_TIME=en_AG.utf8\n") + end + end + context "without lang" do + it "returns a valid string" do + resource.lang() + resource.lc_env({ "LC_TIME" => "en_AG.utf8", "LC_MESSAGES" => "en_AG.utf8" }) + expect(provider.send(:new_content)).to eq("LC_MESSAGES=en_AG.utf8\nLC_TIME=en_AG.utf8\n") + end + end + context "without lc_env" do + it "returns a valid string" do + resource.lang("en_US") + resource.lc_env() + expect(provider.send(:new_content)).to eq("LANG=en_US\n") + end + end + context "without both" do + it "returns string with only new-line character" do + resource.lang() + resource.lc_env() + expect(provider.send(:new_content)).to eq("\n") + end + end + end + + describe "#up_to_date?" do + context "when file does not exists" do + it "returns false" do + allow(File).to receive(:read).and_raise(Errno::ENOENT, "No such file or directory") + expect(provider.send(:up_to_date?)).to be_falsy + end + end + + context "when file exists" do + let(:content) { "LANG=en_US\nLC_MESSAGES=en_AG.utf8\nLC_TIME=en_AG.utf8\n" } + before do + allow(provider).to receive(:new_content).and_return(content) + end + context "but is empty" do + it "returns false" do + allow(File).to receive(:read).and_return("") + expect(provider.send(:up_to_date?)).to be_falsy + end + end + context "and contains old key-vals" do + it "returns false" do + allow(File).to receive(:read).and_return("LC_MESSAGES=en_AG.utf8\n") + expect(provider.send(:up_to_date?)).to be_falsy + end + end + context "and contains new key-vals" do + it "returns true" do + allow(File).to receive(:read).and_return(content) + expect(provider.send(:up_to_date?)).to be_truthy + end + end + end + end +end diff --git a/spec/unit/resource/locale_spec.rb b/spec/unit/resource/locale_spec.rb index 544f6342c5..0479f252b2 100644 --- a/spec/unit/resource/locale_spec.rb +++ b/spec/unit/resource/locale_spec.rb @@ -64,160 +64,4 @@ describe Chef::Resource::Locale do end end end - - describe "#unavailable_locales" do - let(:available_locales) do - <<~LOC - C - C.UTF-8 - en_AG - en_AG.utf8 - en_US - POSIX - LOC - end - before do - dummy = Mixlib::ShellOut.new - allow_any_instance_of(Chef::Mixin::ShellOut).to receive(:shell_out!).with("locale -a").and_return(dummy) - allow(dummy).to receive(:stdout).and_return(available_locales) - end - context "when all locales are available on system" do - context "with both properties" do - it "returns an empty array" do - resource.lang("en_US") - resource.lc_env({ "LC_TIME" => "en_AG.utf8", "LC_MESSAGES" => "en_AG.utf8" }) - expect(provider.unavailable_locales).to eq([]) - end - end - context "without lang" do - it "returns an empty array" do - resource.lang() - resource.lc_env({ "LC_TIME" => "en_AG.utf8", "LC_MESSAGES" => "en_AG.utf8" }) - expect(provider.unavailable_locales).to eq([]) - end - end - context "without lc_env" do - it "returns an empty array" do - resource.lang("en_US") - resource.lc_env() - expect(provider.unavailable_locales).to eq([]) - end - end - context "without both" do - it "returns an empty array" do - resource.lang() - resource.lc_env() - expect(provider.unavailable_locales).to eq([]) - end - end - end - - context "when some locales are not available" do - context "with both properties" do - it "returns list" do - resource.lang("de_DE") - resource.lc_env({ "LC_TIME" => "en_AG.utf8", "LC_MESSAGES" => "en_US.utf8" }) - expect(provider.unavailable_locales).to eq(["de_DE", "en_US.utf8"]) - end - end - context "without lang" do - it "returns list" do - resource.lang() - resource.lc_env({ "LC_TIME" => "en_AG.utf8", "LC_MESSAGES" => "en_US.utf8" }) - expect(provider.unavailable_locales).to eq(["en_US.utf8"]) - end - end - context "without lc_env" do - it "returns list" do - resource.lang("de_DE") - resource.lc_env() - expect(provider.unavailable_locales).to eq(["de_DE"]) - end - end - context "without both" do - it "returns an empty array" do - resource.lang() - resource.lc_env() - expect(provider.unavailable_locales).to eq([]) - end - end - end - end - - describe "#new_content" do - context "with both properties" do - before do - resource.lang("en_US") - resource.lc_env({ "LC_TIME" => "en_AG.utf8", "LC_MESSAGES" => "en_AG.utf8" }) - end - it "returns string" do - expect(provider.new_content).to be_a(String) - expect(provider.new_content).not_to be_empty - end - it "keys will be sorted" do - expect(provider.new_content.split("\n").map { |x| x.split("=") }.collect(&:first)).to eq(%w{LANG LC_MESSAGES LC_TIME}) - end - it "ends with a new-line character" do - expect(provider.new_content[-1]).to eq("\n") - end - it "returns a valid string" do - expect(provider.new_content).to eq("LANG=en_US\nLC_MESSAGES=en_AG.utf8\nLC_TIME=en_AG.utf8\n") - end - end - context "without lang" do - it "returns a valid string" do - resource.lang() - resource.lc_env({ "LC_TIME" => "en_AG.utf8", "LC_MESSAGES" => "en_AG.utf8" }) - expect(provider.new_content).to eq("LC_MESSAGES=en_AG.utf8\nLC_TIME=en_AG.utf8\n") - end - end - context "without lc_env" do - it "returns a valid string" do - resource.lang("en_US") - resource.lc_env() - expect(provider.new_content).to eq("LANG=en_US\n") - end - end - context "without both" do - it "returns string with only new-line character" do - resource.lang() - resource.lc_env() - expect(provider.new_content).to eq("\n") - end - end - end - - describe "#up_to_date?" do - context "when file does not exists" do - it "returns false" do - allow(File).to receive(:read).and_raise(Errno::ENOENT, "No such file or directory") - expect(provider.up_to_date?).to be_falsy - end - end - - context "when file exists" do - let(:content) { "LANG=en_US\nLC_MESSAGES=en_AG.utf8\nLC_TIME=en_AG.utf8\n" } - before do - allow(provider).to receive(:new_content).and_return(content) - end - context "but is empty" do - it "returns false" do - allow(File).to receive(:read).and_return("") - expect(provider.up_to_date?).to be_falsy - end - end - context "and contains old key-vals" do - it "returns false" do - allow(File).to receive(:read).and_return("LC_MESSAGES=en_AG.utf8\n") - expect(provider.up_to_date?).to be_falsy - end - end - context "and contains new key-vals" do - it "returns true" do - allow(File).to receive(:read).and_return(content) - expect(provider.up_to_date?).to be_truthy - end - end - end - end end |