File Manager
Viewing File: plist.rb
#
# Copyright:: Copyright 2017-2020, Microsoft Corporation
# Copyright:: Copyright (c) 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_relative "../resource"
require "plist"
class Chef
class Resource
class PlistResource < Chef::Resource # we name this PlistResource to avoid confusion with Plist from the plist gem
unified_mode true
provides :plist
description "Use the **plist** resource to set config values in plist files on macOS systems."
introduced "16.0"
examples <<~DOC
**Show hidden files in finder**:
```ruby
plist 'show hidden files' do
path '/Users/vagrant/Library/Preferences/com.apple.finder.plist'
entry 'AppleShowAllFiles'
value true
end
```
DOC
property :path, String, name_property: true,
description: "The path on disk to the plist file."
property :entry, String
property :value, [TrueClass, FalseClass, String, Integer, Float, Hash]
property :encoding, String, default: "binary"
property :owner, String, default: "root",
description: "The owner of the plist file."
property :group, String, default: "wheel",
description: "The group of the plist file."
property :mode, [String, Integer],
description: "The file mode of the plist file. Ex: '644'"
PLISTBUDDY_EXECUTABLE = "/usr/libexec/PlistBuddy".freeze
DEFAULTS_EXECUTABLE = "/usr/bin/defaults".freeze
PLUTIL_EXECUTABLE = "/usr/bin/plutil".freeze
PLUTIL_FORMAT_MAP = { "us-ascii" => "xml1",
"text/xml" => "xml1",
"utf-8" => "xml1",
"binary" => "binary1" }.freeze
load_current_value do |desired|
current_value_does_not_exist! unless ::File.exist? desired.path
entry desired.entry if entry_in_plist? desired.entry, desired.path
setting = setting_from_plist desired.entry, desired.path
value convert_to_data_type_from_string(setting[:key_type], setting[:key_value])
file_type_cmd = shell_out "/usr/bin/file", "--brief", "--mime-encoding", "--preserve-date", desired.path
encoding file_type_cmd.stdout.chomp
file_owner_cmd = shell_out("/usr/bin/stat", "-f", "%Su", desired.path)
owner file_owner_cmd.stdout.chomp
file_group_cmd = shell_out("/usr/bin/stat", "-f", "%Sg", desired.path)
group file_group_cmd.stdout.chomp
end
action :set do
converge_if_changed :path do
converge_by "create new plist: '#{new_resource.path}'" do
file new_resource.path do
content {}.to_plist
owner new_resource.owner
group new_resource.group
mode new_resource.mode if property_is_set?(:mode)
end
end
end
plist_file_name = ::File.basename(new_resource.path)
converge_if_changed :entry do
converge_by "add entry \"#{new_resource.entry}\" to #{plist_file_name}" do
shell_out!(plistbuddy_command(:add, new_resource.entry, new_resource.path, new_resource.value))
end
end
converge_if_changed :value do
converge_by "#{plist_file_name}: set #{new_resource.entry} to #{new_resource.value}" do
shell_out!(plistbuddy_command(:set, new_resource.entry, new_resource.path, new_resource.value))
end
end
converge_if_changed :encoding do
converge_by "change format" do
unless PLUTIL_FORMAT_MAP.key?(new_resource.encoding)
Chef::Application.fatal!(
"Option encoding must be equal to one of: #{PLUTIL_FORMAT_MAP.keys}! You passed \"#{new_resource.encoding}\"."
)
end
shell_out!(PLUTIL_EXECUTABLE, "-convert", PLUTIL_FORMAT_MAP[new_resource.encoding], new_resource.path)
end
end
converge_if_changed :owner do
converge_by "update owner to #{new_resource.owner}" do
file new_resource.path do
owner new_resource.owner
end
end
end
converge_if_changed :group do
converge_by "update group to #{new_resource.group}" do
file new_resource.path do
group new_resource.group
end
end
end
end
### Question: Should I refactor these methods into an action_class?
### Answer: NO
### Why: We need them in both the action and in load_current_value. If you put them in the
### action class then they're only in the Provider class and are not available to load_current_value
def convert_to_data_type_from_string(type, value)
case type
when "boolean"
# Since we've determined this is a boolean data type, we can assume that:
# If the value as an int is 1, return true
# If the value as an int is 0 (not 1), return false
value.to_i == 1
when "integer"
value.to_i
when "float"
value.to_f
when "string"
value
when "dictionary"
value
when nil
""
else
raise "Unknown or unsupported data type: #{type.class}"
end
end
def type_to_commandline_string(value)
case value
when Array
"array"
when Integer
"integer"
when FalseClass
"bool"
when TrueClass
"bool"
when Hash
"dict"
when String
"string"
when Float
"float"
else
raise "Unknown or unsupported data type: #{value} of #{value.class}"
end
end
def entry_in_plist?(entry, path)
print_entry = plistbuddy_command :print, entry, path
cmd = shell_out print_entry
cmd.exitstatus == 0
end
def plistbuddy_command(subcommand, entry, path, value = nil)
sep = " "
arg = case subcommand.to_s
when "add"
type_to_commandline_string(value)
when "set"
if value.is_a?(Hash)
sep = ":"
value.map { |k, v| "#{k} #{v}" }
else
value
end
else
""
end
entry_with_arg = ["\"#{entry}\"", arg].join(sep).strip
subcommand = "#{subcommand.capitalize} :#{entry_with_arg}"
[PLISTBUDDY_EXECUTABLE, "-c", "\'#{subcommand}\'", "\"#{path}\""].join(" ")
end
def setting_from_plist(entry, path)
defaults_read_type_output = shell_out(DEFAULTS_EXECUTABLE, "read-type", path, entry).stdout
data_type = defaults_read_type_output.split.last
if value.class == Hash
plutil_output = shell_out(PLUTIL_EXECUTABLE, "-extract", entry, "xml1", "-o", "-", path).stdout.chomp
{ key_type: data_type, key_value: ::Plist.parse_xml(plutil_output) }
else
defaults_read_output = shell_out(DEFAULTS_EXECUTABLE, "read", path, entry).stdout
{ key_type: data_type, key_value: defaults_read_output.strip }
end
end
end
end
end