diff --git a/libraries/plistbuddy.rb b/libraries/plistbuddy.rb new file mode 100644 index 00000000..e4fe683d --- /dev/null +++ b/libraries/plistbuddy.rb @@ -0,0 +1,32 @@ +module MacOS + module PlistBuddyHelpers + def convert_to_string_from_data_type(value) + data_type_cases = { Array => "array #{value}", + Integer => "int #{value}", + TrueClass => "bool #{value}", + FalseClass => "bool #{value}", + Hash => "dict #{value}", + String => "string #{value}", + Float => "float #{value}" } + data_type_cases[value.class] + end + + def format_plistbuddy_command(action_property, plist_entry, plist_value = nil) + plist_value = args_formatter(action_property, plist_value) + "/usr/libexec/Plistbuddy -c \'#{action_property.to_s.capitalize} :#{plist_entry} #{plist_value}\'" + end + + private + + def args_formatter(action_property, plist_value) + if action_property == :add + convert_to_string_from_data_type plist_value + else + plist_value + end + end + end +end + +Chef::Recipe.include(MacOS::PlistBuddyHelpers) +Chef::Resource.include(MacOS::PlistBuddyHelpers) diff --git a/resources/plistbuddy.rb b/resources/plistbuddy.rb new file mode 100644 index 00000000..fa052905 --- /dev/null +++ b/resources/plistbuddy.rb @@ -0,0 +1,34 @@ +resource_name :plistbuddy + +property :path, String, name_property: true +property :entry, String, required: true +property :value, [Hash, String, Array, TrueClass, FalseClass, Integer, Float] + +default_action :set + +action_class do + def entry_missing? + command = format_plistbuddy_command(:print, new_resource.entry, new_resource.value) + full_command = command + ' ' + new_resource.path + shell_out(full_command).error? + end + + def current_entry_value + command = format_plistbuddy_command(:print, new_resource.entry) + full_command = command + ' ' + new_resource.path + shell_out(full_command).stdout.chomp + end +end + +action :set do + if entry_missing? + execute format_plistbuddy_command(:add, new_resource.entry, new_resource.value) + ' ' + new_resource.path + execute format_plistbuddy_command(:set, new_resource.entry, new_resource.value) + ' ' + new_resource.path + elsif current_entry_value != new_resource.value.to_s + execute format_plistbuddy_command(:set, new_resource.entry, new_resource.value) + ' ' + new_resource.path + end +end + +action :delete do + execute format_plistbuddy_command(:delete, new_resource.entry, new_resource.value) + ' ' + new_resource.path +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1dd5126b..08509c91 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,2 +1,5 @@ require 'chefspec' require 'chefspec/berkshelf' + +require_relative '../libraries/plistbuddy' +require_relative '../libraries/xcode' diff --git a/spec/unit/libraries/plistbuddy_spec.rb b/spec/unit/libraries/plistbuddy_spec.rb new file mode 100644 index 00000000..ac11c30c --- /dev/null +++ b/spec/unit/libraries/plistbuddy_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +include MacOS::PlistBuddyHelpers + +describe MacOS::PlistBuddyHelpers, '#format_plistbuddy_command' do + context 'Adding a value to a plist' do + it 'the bool arguments contain the data type' do + expect(format_plistbuddy_command(:add, 'FooEntry', true)).to eq "/usr/libexec/Plistbuddy -c 'Add :FooEntry bool true'" + end + + it 'the int arguments contain the data type' do + expect(format_plistbuddy_command(:add, 'QuuxEntry', 50)).to eq "/usr/libexec/Plistbuddy -c 'Add :QuuxEntry int 50'" + end + + it 'the delete command is formatted properly' do + expect(format_plistbuddy_command(:delete, 'BarEntry')).to eq "/usr/libexec/Plistbuddy -c 'Delete :BarEntry '" + end + + it 'the set command is formatted properly' do + expect(format_plistbuddy_command(:set, 'BazEntry', false)).to eq "/usr/libexec/Plistbuddy -c 'Set :BazEntry false'" + end + + it 'the print command is formatted properly' do + expect(format_plistbuddy_command(:print, 'QuxEntry')).to eq "/usr/libexec/Plistbuddy -c 'Print :QuxEntry '" + end + end +end + +describe MacOS::PlistBuddyHelpers, '#convert_to_string_from_data_type' do + context 'When given a certain data type' do + it 'returns the required PlistBuddy boolean entry' do + expect(convert_to_string_from_data_type(true)).to eq 'bool true' + end + + xit 'returns the required PlistBuddy array entry' do # TODO: Implement proper plist array syntax (i.e. containers) + expect(convert_to_string_from_data_type(%w(foo bar))).to eq 'array foo bar' + end + + xit 'returns the required PlistBuddy dictionary entry' do # TODO: Implement proper plist dict syntax (i.e. containers) + expect(convert_to_string_from_data_type('baz' => 'qux')).to eq 'dict key value' + end + + it 'returns the required PlistBuddy string entry' do + expect(convert_to_string_from_data_type('quux')).to eq 'string quux' + end + + it 'returns the required PlistBuddy int entry' do + expect(convert_to_string_from_data_type(1)).to eq 'int 1' + end + + it 'returns the required PlistBuddy float entry' do + expect(convert_to_string_from_data_type(1.0)).to eq 'float 1.0' + end + end +end diff --git a/spec/unit/recipes/plistbuddy_spec.rb b/spec/unit/recipes/plistbuddy_spec.rb new file mode 100644 index 00000000..df3ad1c3 --- /dev/null +++ b/spec/unit/recipes/plistbuddy_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe 'macos::default' do + context 'When all attributes are default, on macOS 10.12' do + let(:chef_run) do + runner = ChefSpec::ServerRunner.new(platform: 'mac_os_x', version: '10.12') + runner.converge(described_recipe) + end + + it 'converges successfully' do + expect { chef_run }.to_not raise_error + end + end +end diff --git a/test/cookbooks/plistbuddy/.gitignore b/test/cookbooks/plistbuddy/.gitignore new file mode 100644 index 00000000..17de6c82 --- /dev/null +++ b/test/cookbooks/plistbuddy/.gitignore @@ -0,0 +1,103 @@ +*~ +*# +.#* +\#*# +.*.sw[a-z] +*.un~ + +# Bundler +Gemfile +Gemfile.lock +bin/* +.bundle/* + +# test kitchen +.kitchen/ +.kitchen.local.yml + +# Chef +Berksfile.lock +.zero-knife.rb +Policyfile.lock.json +.autotest +coverage +.DS_Store +pkg/* +tags +*/tags +.chef +results + +# You should check in your Gemfile.lock in applications, and not in gems +external_tests/*.lock +/Gemfile.local + +# ignore some common Bundler 'binstubs' directory names +# http://gembundler.com/man/bundle-exec.1.html +b/ +binstubs/ +.bundle +# RVM and RBENV ruby version files +.rbenv-version +.rvmrc +.ruby-version +.ruby-gemset + +# IDE files +.project + +# Documentation +_site/* +.yardoc/ +doc/ + +# Kitchen Tests Local Mode Data +kitchen-tests/nodes/* + +# Temporary files present during spec runs +spec/data/test-dir +spec/data/nodes +/config/ + +# acceptance binstubs +acceptance/bin/* + +vendor/ +acceptance/vendor +kitchen-tests/vendor + +# Visual Studio Code files +.vscode +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +.idea + +# CMake +cmake-build-debug/ + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Testing +*.box +berks-cookbooks +Vagrantfile +.rubocop.yml +.vagrant +data_bags diff --git a/test/cookbooks/plistbuddy/Berksfile b/test/cookbooks/plistbuddy/Berksfile new file mode 100644 index 00000000..0fac3b01 --- /dev/null +++ b/test/cookbooks/plistbuddy/Berksfile @@ -0,0 +1,5 @@ +source 'https://supermarket.chef.io' + +metadata + +cookbook 'macos', path: '../../../../macos/' diff --git a/test/cookbooks/plistbuddy/metadata.rb b/test/cookbooks/plistbuddy/metadata.rb new file mode 100644 index 00000000..21d229d5 --- /dev/null +++ b/test/cookbooks/plistbuddy/metadata.rb @@ -0,0 +1,4 @@ +name 'plistbuddy' +version '1.0.0' + +depends 'macos', '0.8.4' diff --git a/test/cookbooks/plistbuddy/recipes/customize_dock.rb b/test/cookbooks/plistbuddy/recipes/customize_dock.rb new file mode 100644 index 00000000..73f05800 --- /dev/null +++ b/test/cookbooks/plistbuddy/recipes/customize_dock.rb @@ -0,0 +1,4 @@ +plistbuddy '/Users/vagrant/Library/Preferences/com.apple.dock.plist' do + entry 'showMissionControlGestureEnabled' + value false +end diff --git a/test/cookbooks/plistbuddy/recipes/default.rb b/test/cookbooks/plistbuddy/recipes/default.rb new file mode 100644 index 00000000..0d12f25f --- /dev/null +++ b/test/cookbooks/plistbuddy/recipes/default.rb @@ -0,0 +1,2 @@ +include_recipe 'plistbuddy::customize_dock' +include_recipe 'plistbuddy::show_hidden_files' diff --git a/test/cookbooks/plistbuddy/recipes/show_hidden_files.rb b/test/cookbooks/plistbuddy/recipes/show_hidden_files.rb new file mode 100644 index 00000000..5dcd3662 --- /dev/null +++ b/test/cookbooks/plistbuddy/recipes/show_hidden_files.rb @@ -0,0 +1,4 @@ +plistbuddy '/Users/vagrant/Library/Preferences/com.apple.finder.plist' do + entry 'AppleShowAllFiles' + value true +end diff --git a/test/cookbooks/plistbuddy/test/smoke/default/default_test.rb b/test/cookbooks/plistbuddy/test/smoke/default/default_test.rb new file mode 100644 index 00000000..c86f1025 --- /dev/null +++ b/test/cookbooks/plistbuddy/test/smoke/default/default_test.rb @@ -0,0 +1,7 @@ +describe command("/usr/libexec/PlistBuddy -c 'Print :showMissionControlGestureEnabled' /Users/vagrant/Library/Preferences/com.apple.dock.plist") do + its('stdout') { should match 'false' } +end + +describe command("/usr/libexec/PlistBuddy -c 'Print :AppleShowAllFiles' /Users/vagrant/Library/Preferences/com.apple.finder.plist") do + its('stdout') { should match 'true' } +end diff --git a/test/smoke/default/xcode_test.rb b/test/smoke/default/xcode_test.rb index b34ada6a..e3b7cd61 100644 --- a/test/smoke/default/xcode_test.rb +++ b/test/smoke/default/xcode_test.rb @@ -11,6 +11,6 @@ end describe command('/usr/local/bin/xcversion simulators') do - its('stdout') { should match /iOS 10\.3\.1 Simulator \(installed\)/ } + its('stdout') { should match(/iOS 10\.3\.1 Simulator \(installed\)/) } end end