summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAmulya <logwolvy+github@gmail.com>2021-07-06 20:15:17 +0530
committerAmulya <logwolvy+github@gmail.com>2021-07-06 20:15:17 +0530
commit66d932f7e5a331434e9353af7788f596824e3eb4 (patch)
treefed95414dde954c5f2cb4110098e23afb69c5e7e
parent8457513756bfaed5e23b581990da023dd21aeb7b (diff)
downloadchef-66d932f7e5a331434e9353af7788f596824e3eb4.tar.gz
POC: Core Foundation based macos_userdefaults rewritecf-based-macos-userdefaults-rewrite
Signed-off-by: Amulya <atomer@progress.com>
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock12
-rw-r--r--chef.gemspec2
-rw-r--r--lib/chef/resource/macos_userdefaults.rb154
-rw-r--r--spec/unit/resource/macos_user_defaults_spec.rb91
5 files changed, 72 insertions, 189 deletions
diff --git a/Gemfile b/Gemfile
index be03e4e48a..8ff999173c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -7,6 +7,8 @@ gem "ohai", git: "https://github.com/chef/ohai.git", branch: "master"
gem "chef-utils", path: File.expand_path("chef-utils", __dir__) if File.exist?(File.expand_path("chef-utils", __dir__))
gem "chef-config", path: File.expand_path("chef-config", __dir__) if File.exist?(File.expand_path("chef-config", __dir__))
+gem "corefoundation", git: "https://github.com/chef/corefoundation.git"
+
if File.exist?(File.expand_path("chef-bin", __dir__))
# bundling in a git checkout
gem "chef-bin", path: File.expand_path("chef-bin", __dir__)
diff --git a/Gemfile.lock b/Gemfile.lock
index 22e620861e..d04baf52a6 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -7,6 +7,13 @@ GIT
rubocop (= 1.17.0)
GIT
+ remote: https://github.com/chef/corefoundation.git
+ revision: 7df25155153ca6b5a14a494e63cfb2b8f7548f8b
+ specs:
+ corefoundation (0.2.0)
+ ffi
+
+GIT
remote: https://github.com/chef/ohai.git
revision: faf25314310a9ce27a7ba3e73ffb8301633dec83
branch: master
@@ -41,6 +48,7 @@ PATH
chef-utils (= 17.3.16)
chef-vault
chef-zero (>= 14.0.11)
+ corefoundation
diff-lcs (>= 1.2.4, < 1.4.0)
erubis (~> 2.7)
ffi (>= 1.5.0)
@@ -68,6 +76,7 @@ PATH
chef-utils (= 17.3.16)
chef-vault
chef-zero (>= 14.0.11)
+ corefoundation
diff-lcs (>= 1.2.4, < 1.4.0)
erubis (~> 2.7)
ffi (>= 1.5.0)
@@ -419,6 +428,7 @@ DEPENDENCIES
chef-vault
cheffish (>= 17)
chefstyle!
+ corefoundation!
fauxhai-ng
inspec-core-bin (~> 4.24)
ohai!
@@ -432,4 +442,4 @@ DEPENDENCIES
webmock
BUNDLED WITH
- 2.2.19
+ 2.2.20
diff --git a/chef.gemspec b/chef.gemspec
index 76f562cc71..45d6f6b941 100644
--- a/chef.gemspec
+++ b/chef.gemspec
@@ -55,6 +55,8 @@ Gem::Specification.new do |s|
s.add_dependency "proxifier", "~> 1.0"
+ s.add_dependency "corefoundation"
+
s.bindir = "bin"
s.executables = %w{ }
diff --git a/lib/chef/resource/macos_userdefaults.rb b/lib/chef/resource/macos_userdefaults.rb
index 036bd96d8b..37ad5e5a9c 100644
--- a/lib/chef/resource/macos_userdefaults.rb
+++ b/lib/chef/resource/macos_userdefaults.rb
@@ -17,6 +17,7 @@
require_relative "../resource"
require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+require "corefoundation"
autoload :Plist, "plist"
class Chef
@@ -89,10 +90,11 @@ class Chef
property :type, String,
description: "The value type of the preference key.",
+ deprecated: true,
equal_to: %w{bool string int float array dict},
desired_state: false
- property :user, String,
+ property :user, [String, Symbol],
description: "The system user that the default will be applied to.",
desired_state: false
@@ -102,50 +104,20 @@ class Chef
desired_state: false
load_current_value do |new_resource|
- Chef::Log.debug "#load_current_value: shelling out \"#{defaults_export_cmd(new_resource).join(" ")}\" to determine state"
- state = shell_out(defaults_export_cmd(new_resource), user: new_resource.user)
-
- if state.error? || state.stdout.empty?
- Chef::Log.debug "#load_current_value: #{defaults_export_cmd(new_resource).join(" ")} returned stdout: #{state.stdout} and stderr: #{state.stderr}"
- current_value_does_not_exist!
- end
+ state = read_preferences(new_resource)
- plist_data = ::Plist.parse_xml(state.stdout)
-
- # handle the situation where the key doesn't exist in the domain
- if plist_data.key?(new_resource.key)
- key new_resource.key
- else
+ # TODO: error handling
+ unless state
+ Chef::Log.debug "#{new_resource.key} could not be found in the domain"
current_value_does_not_exist!
end
- value plist_data[new_resource.key]
- end
-
- #
- # The defaults command to export a domain
- #
- # @return [Array] defaults command
- #
- def defaults_export_cmd(resource)
- state_cmd = ["/usr/bin/defaults"]
-
- if resource.host == "current"
- state_cmd.concat(["-currentHost"])
- elsif resource.host # they specified a non-nil value, which is a hostname
- state_cmd.concat(["-host", resource.host])
- end
-
- state_cmd.concat(["export", resource.domain, "-"])
- state_cmd
+ value state
end
action :write, description: "Write the value to the specified domain/key." do
converge_if_changed do
- cmd = defaults_modify_cmd
- Chef::Log.debug("Updating defaults value by shelling out: #{cmd.join(" ")}")
-
- shell_out!(cmd, user: new_resource.user)
+ write_preferences(new_resource)
end
end
@@ -153,98 +125,42 @@ class Chef
# if it's not there there's nothing to remove
return unless current_resource
- converge_by("delete domain:#{new_resource.domain} key:#{new_resource.key}") do
+ # TODO: implement in CF and use here
+ end
- cmd = defaults_modify_cmd
- Chef::Log.debug("Removing defaults key by shelling out: #{cmd.join(" ")}")
+ action_class do
+ CF_MAPPING = {
+ current_user: CF::Preferences::CURRENT_USER,
+ all_users: CF::Preferences::ALL_USERS,
+ current_host: CF::Preferences::CURRENT_HOST,
+ all_hosts: CF::Preferences::ALL_HOSTS,
+ current: CF::Preferences::CURRENT_HOST # TODO: deprecation warning for this option
+ }
+
+ def read_preferences(new_resource)
+ CF::Preferences.get(new_resource.key, new_resource.domain, mapped_user, mapped_host)
+ end
- shell_out!(cmd, user: new_resource.user)
+ def write_preferences(new_resource)
+ CF::Preferences.set(new_resource.key, new_resource.value, new_resource.domain, mapped_user, mapped_host)
end
- end
- action_class do
- #
- # The command used to write or delete delete values from domains
- #
- # @return [Array] Array representation of defaults command to run
- #
- def defaults_modify_cmd
- cmd = ["/usr/bin/defaults"]
-
- if new_resource.host == :current
- cmd.concat(["-currentHost"])
- elsif new_resource.host # they specified a non-nil value, which is a hostname
- cmd.concat(["-host", new_resource.host])
- end
-
- cmd.concat([action.to_s, new_resource.domain, new_resource.key])
- cmd.concat(processed_value) if action == :write
- cmd.prepend("sudo") if new_resource.sudo
- cmd
+ def mapped_user
+ CF_MAPPING[valid_user.to_sym] || valid_user.to_s
end
- #
- # convert the provided value into the format defaults expects
- #
- # @return [array] array of values starting with the type if applicable
- #
- def processed_value
- type = new_resource.type || value_type(new_resource.value)
-
- # when dict this creates an array of values ["Key1", "Value1", "Key2", "Value2" ...]
- cmd_values = ["-#{type}"]
-
- case type
- when "dict"
- cmd_values.concat(new_resource.value.flatten)
- when "array"
- cmd_values.concat(new_resource.value)
- when "bool"
- cmd_values.concat(bool_to_defaults_bool(new_resource.value))
- else
- cmd_values.concat([new_resource.value])
- end
-
- cmd_values
+ def mapped_host
+ CF_MAPPING[valid_host.to_sym] || valid_host.to_s
end
- #
- # defaults booleans on the CLI must be 'TRUE' or 'FALSE' so convert various inputs to that
- #
- # @param [String, Integer, Boolean] input <description>
- #
- # @return [String] TRUE or FALSE
- #
- def bool_to_defaults_bool(input)
- return ["TRUE"] if [true, "TRUE", "1", "true", "YES", "yes"].include?(input)
- return ["FALSE"] if [false, "FALSE", "0", "false", "NO", "no"].include?(input)
-
- # make sure it's very clear bad input was given
- raise ArgumentError, "#{input} cannot be converted to a boolean value for use with Apple's defaults command. Acceptable values are: 'TRUE', 'YES', 'true, 'yes', '0', true, 'FALSE', 'false', 'NO', 'no', '1', or false."
+ def valid_user
+ # TODO: check backward compatibility and defaults util convention
+ new_resource.user || :current_user
end
- #
- # convert ruby type to defaults type
- #
- # @param [Integer, Float, String, TrueClass, FalseClass, Hash, Array] value The value being set
- #
- # @return [string, nil] the type value used by defaults or nil if not applicable
- #
- def value_type(value)
- case value
- when true, false
- "bool"
- when Integer
- "int"
- when Float
- "float"
- when Hash
- "dict"
- when Array
- "array"
- when String
- "string"
- end
+ def valid_host
+ # TODO: check backward compatibility and defaults util convention
+ new_resource.host || :all_hosts
end
end
end
diff --git a/spec/unit/resource/macos_user_defaults_spec.rb b/spec/unit/resource/macos_user_defaults_spec.rb
index 2c643ab266..883c9b6b85 100644
--- a/spec/unit/resource/macos_user_defaults_spec.rb
+++ b/spec/unit/resource/macos_user_defaults_spec.rb
@@ -51,86 +51,39 @@ describe Chef::Resource::MacosUserDefaults do
expect { resource.action :write }.not_to raise_error
end
- describe "#defaults_export_cmd" do
- it "exports NSGlobalDomain if no domain is set" do
- expect(provider.defaults_export_cmd(resource)).to eq(["/usr/bin/defaults", "export", "NSGlobalDomain", "-"])
+ describe '#mapped_host' do
+ it "uses `all_hosts` as default" do
+ expect(provider.mapped_host).to eq CF::Preferences::ALL_HOSTS
end
- it "exports a provided domain" do
- resource.domain "com.tim"
- expect(provider.defaults_export_cmd(resource)).to eq(["/usr/bin/defaults", "export", "com.tim", "-"])
- end
-
- it "sets -currentHost if host is 'current'" do
- resource.host "current"
- expect(provider.defaults_export_cmd(resource)).to eq(["/usr/bin/defaults", "-currentHost", "export", "NSGlobalDomain", "-"])
- end
-
- it "sets -host 'tim-laptop if host is 'tim-laptop'" do
- resource.host "tim-laptop"
- expect(provider.defaults_export_cmd(resource)).to eq(["/usr/bin/defaults", "-host", "tim-laptop", "export", "NSGlobalDomain", "-"])
- end
- end
-
- describe "#defaults_modify_cmd" do
- # avoid needing to set these required values over and over. We'll overwrite them where necessary
- before do
- resource.key = "foo"
- resource.value = "bar"
- end
-
- it "writes to NSGlobalDomain if domain isn't specified" do
- expect(provider.defaults_modify_cmd).to eq(["/usr/bin/defaults", "write", "NSGlobalDomain", "foo", "-string", "bar"])
- end
-
- it "uses the domain property if set" do
- resource.domain = "MyCustomDomain"
- expect(provider.defaults_modify_cmd).to eq(["/usr/bin/defaults", "write", "MyCustomDomain", "foo", "-string", "bar"])
- end
-
- it "sets host specific values using host property" do
- resource.host = "tims_laptop"
- expect(provider.defaults_modify_cmd).to eq(["/usr/bin/defaults", "-host", "tims_laptop", "write", "NSGlobalDomain", "foo", "-string", "bar"])
- end
-
- it "if host is set to :current it passes CurrentHost" do
+ it "maps `current` to corresponding constant" do
resource.host = :current
- expect(provider.defaults_modify_cmd).to eq(["/usr/bin/defaults", "-currentHost", "write", "NSGlobalDomain", "foo", "-string", "bar"])
+ expect(provider.mapped_host).to eq CF::Preferences::CURRENT_HOST
end
- it "raises ArgumentError if bool is specified, but the value can't be made into a bool" do
- resource.type "bool"
- expect { provider.defaults_modify_cmd }.to raise_error(ArgumentError)
- end
-
- it "autodetects array type and passes individual values" do
- resource.value = %w{one two three}
- expect(provider.defaults_modify_cmd).to eq(["/usr/bin/defaults", "write", "NSGlobalDomain", "foo", "-array", "one", "two", "three"])
- end
-
- it "autodetects string type and passes a single value" do
- resource.value = "one"
- expect(provider.defaults_modify_cmd).to eq(["/usr/bin/defaults", "write", "NSGlobalDomain", "foo", "-string", "one"])
- end
-
- it "autodetects integer type and passes a single value" do
- resource.value = 1
- expect(provider.defaults_modify_cmd).to eq(["/usr/bin/defaults", "write", "NSGlobalDomain", "foo", "-int", 1])
+ it "maps `current_host` to correct corresponding constant" do
+ resource.host = :current_host
+ expect(provider.mapped_host).to eq CF::Preferences::CURRENT_HOST
end
+ end
- it "autodetects boolean type from TrueClass value and passes a 'TRUE' string" do
- resource.value = true
- expect(provider.defaults_modify_cmd).to eq(["/usr/bin/defaults", "write", "NSGlobalDomain", "foo", "-bool", "TRUE"])
+ describe '#mapped_user' do
+ it "uses `current_user` as default" do
+ expect(provider.mapped_user).to eq CF::Preferences::CURRENT_USER
end
- it "autodetects boolean type from FalseClass value and passes a 'FALSE' string" do
- resource.value = false
- expect(provider.defaults_modify_cmd).to eq(["/usr/bin/defaults", "write", "NSGlobalDomain", "foo", "-bool", "FALSE"])
+ it "maps `all_users` to corresponding constant" do
+ resource.user = :all_users
+ expect(provider.mapped_user).to eq CF::Preferences::ALL_USERS
end
+ end
- it "autodetects dict type from Hash value and flattens keys & values" do
- resource.value = { "foo" => "bar" }
- expect(provider.defaults_modify_cmd).to eq(["/usr/bin/defaults", "write", "NSGlobalDomain", "foo", "-dict", "foo", "bar"])
+ # TODO: should be a functional/integration test
+ describe "#read_preferences" do
+ it "reads preference/state" do
+ resource.domain = "NSGlobalDomain"
+ resource.key = "AppleKeyboardUIMode"
+ expect(provider.read_preferences(resource)).to be_nil
end
end
end