From e05e7b485463ea2c9346a83ad1bbc095cc95d4df Mon Sep 17 00:00:00 2001 From: Nate Walck Date: Mon, 5 Oct 2015 16:07:02 -0700 Subject: Added profile provider for OS X Fixed resource to use let --- chef.gemspec | 1 + lib/chef/provider/osx_profile.rb | 254 +++++++++++++++++++++++++++++++ lib/chef/providers.rb | 1 + lib/chef/resource/osx_profile.rb | 74 +++++++++ lib/chef/resources.rb | 1 + spec/unit/provider/osx_profile_spec.rb | 267 +++++++++++++++++++++++++++++++++ spec/unit/provider_resolver_spec.rb | 1 + spec/unit/resource/osx_profile_spec.rb | 61 ++++++++ 8 files changed, 660 insertions(+) create mode 100644 lib/chef/provider/osx_profile.rb create mode 100644 lib/chef/resource/osx_profile.rb create mode 100644 spec/unit/provider/osx_profile_spec.rb create mode 100644 spec/unit/resource/osx_profile_spec.rb diff --git a/chef.gemspec b/chef.gemspec index dad33623af..5200f7cf3e 100644 --- a/chef.gemspec +++ b/chef.gemspec @@ -42,6 +42,7 @@ Gem::Specification.new do |s| s.add_dependency "specinfra", "~> 2.10" s.add_dependency "syslog-logger", "~> 1.6" + s.add_dependency "uuidtools", "~> 2.1.5" s.add_dependency "proxifier", "~> 1.0" diff --git a/lib/chef/provider/osx_profile.rb b/lib/chef/provider/osx_profile.rb new file mode 100644 index 0000000000..ee355fd38e --- /dev/null +++ b/lib/chef/provider/osx_profile.rb @@ -0,0 +1,254 @@ +# +# Author:: Nate Walck () +# Copyright:: Copyright (c) 2015 Facebook, 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 'chef/log' +require 'chef/provider' +require 'chef/resource' +require 'chef/resource/file' +require 'uuidtools' + +class Chef + class Provider + class OsxProfile < Chef::Provider + include Chef::Mixin::Command + provides :osx_profile, os: "darwin" + provides :osx_config_profile, os: "darwin" + + def whyrun_supported? + true + end + + def load_current_resource + @current_resource = Chef::Resource::OsxProfile.new(@new_resource.name) + @current_resource.profile_name(@new_resource.profile_name) + + all_profiles = get_installed_profiles + @new_resource.profile( + @new_resource.profile || + @new_resource.profile_name + ) + + @new_profile_hash = get_profile_hash(@new_resource.profile) + @new_profile_hash['PayloadUUID'] = + config_uuid(@new_profile_hash) if @new_profile_hash + + if @new_profile_hash + @new_profile_identifier = @new_profile_hash['PayloadIdentifier'] + else + @new_profile_identifier = @new_resource.identifier || + @new_resource.profile_name + end + + current_profile = all_profiles['_computerlevel'].find { + |item| item['ProfileIdentifier'] == + @new_profile_identifier + } + @current_resource.profile(current_profile) + + end + + def define_resource_requirements + requirements.assert(:remove) do |a| + if @new_profile_identifier + a.assertion { + !@new_profile_identifier.nil? and + !@new_profile_identifier.end_with?('.mobileconfig') and + /^\w+(?:\.\w+)+$/.match(@new_profile_identifier) + } + a.failure_message RuntimeError, "when removing using the identifier attribute, it must match the profile identifier" + else + new_profile_name = @new_resource.profile_name + a.assertion { + !new_profile_name.end_with?('.mobileconfig') and + /^\w+(?:\.\w+)+$/.match(new_profile_name) + } + a.failure_message RuntimeError, "When removing by resource name, it must match the profile identifier " + end + end + + requirements.assert(:install) do |a| + if @new_profile_hash.is_a?(Hash) + a.assertion { + @new_profile_hash.include?('PayloadIdentifier') + } + a.failure_message RuntimeError, "The specified profile does not seem to be valid" + end + if @new_profile_hash.is_a?(String) + a.assertion { + @new_profile_hash.end_with?('.mobileconfig') + } + a.failure_message RuntimeError, "#{new_profile_hash}' is not a valid profile" + end + end + end + + def action_install + unless profile_installed? + converge_by("install profile #{@new_profile_identifier}") do + profile_path = write_profile_to_disk + install_profile(profile_path) + get_installed_profiles(true) + end + end + end + + def action_remove + # Clean up profile after removing it + if profile_installed? + converge_by("remove profile #{@new_profile_identifier}") do + remove_profile + get_installed_profiles(true) + end + end + end + + def load_profile_hash(new_profile) + # file must exist in cookbook + if new_profile.end_with?('.mobileconfig') + unless cookbook_file_available?(new_profile) + error_string = "#{self.to_s}: '#{new_profile}' not found in cookbook" + raise Chef::Exceptions::FileNotFound, error_string + end + cookbook_profile = cache_cookbook_profile(new_profile) + return read_plist(cookbook_profile) + else + return nil + end + end + + def cookbook_file_available?(cookbook_file) + run_context.has_cookbook_file_in_cookbook?( + @new_resource.cookbook_name, cookbook_file + ) + end + + def get_cache_dir + cache_dir = Chef::FileCache.create_cache_path( + "profiles/#{@new_resource.cookbook_name}" + ) + end + + def cache_cookbook_profile(cookbook_file) + Chef::FileCache.create_cache_path( + ::File.join( + "profiles", + @new_resource.cookbook_name, + ::File.dirname(cookbook_file) + ) + ) + remote_file = Chef::Resource::CookbookFile.new( + ::File.join( + get_cache_dir, + "#{cookbook_file}.remote" + ), + run_context + ) + remote_file.cookbook_name = @new_resource.cookbook_name + remote_file.source(cookbook_file) + remote_file.backup(false) + remote_file.run_action(:create) + remote_file.path + end + + def get_profile_hash(new_profile) + if new_profile.is_a?(Hash) + return new_profile + elsif new_profile.is_a?(String) + return load_profile_hash(new_profile) + end + end + + def config_uuid(profile) + # Make a UUID of the profile contents and return as string + UUIDTools::UUID.sha1_create( + UUIDTools::UUID_DNS_NAMESPACE, + profile.to_s + ).to_s + end + + def write_profile_to_disk + @new_resource.path(Chef::FileCache.create_cache_path("profiles")) + tempfile = Chef::FileContentManagement::Tempfile.new(@new_resource).tempfile + tempfile.write(@new_profile_hash.to_plist) + tempfile.close + tempfile.path + end + + def install_profile(profile_path) + cmd = "profiles -I -F '#{profile_path}'" + Chef::Log.debug("cmd: #{cmd}") + shellout_results = shell_out(cmd) + shellout_results.exitstatus + end + + def remove_profile + cmd = "profiles -R -p '#{@new_profile_identifier}'" + Chef::Log.debug("cmd: #{cmd}") + shellout_results = shell_out(cmd) + shellout_results.exitstatus + end + + def get_installed_profiles(update=nil) + if update + node.run_state[:config_profiles] = query_installed_profiles + else + node.run_state[:config_profiles] ||= query_installed_profiles + end + end + + def query_installed_profiles + # Dump all profile metadata to a tempfile + tempfile = generate_tempfile + write_installed_profiles(tempfile) + installed_profiles = read_plist(tempfile) + Chef::Log.debug("Saved profiles to run_state") + # Clean up the temp file as we do not need it anymore + ::File.unlink(tempfile) + installed_profiles + end + + def generate_tempfile + tempfile = ::Dir::Tmpname.create("allprofiles.plist") {} + end + + def write_installed_profiles(tempfile) + cmd = "profiles -P -o '#{tempfile}'" + shell_out!(cmd) + end + + def read_plist(xml_file) + Plist::parse_xml(xml_file) + end + + def profile_installed? + # Profile Identifier and UUID must match a currently installed profile + if @current_resource.profile.nil? or @current_resource.profile.empty? + false + else + if @new_resource.action.include?(:remove) + true + else + @current_resource.profile['ProfileUUID'] == + @new_profile_hash['PayloadUUID'] + end + end + end + + end + end +end diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb index 18500d4669..f5e7a0f989 100644 --- a/lib/chef/providers.rb +++ b/lib/chef/providers.rb @@ -41,6 +41,7 @@ require 'chef/provider/mdadm' require 'chef/provider/mount' require 'chef/provider/package' require 'chef/provider/powershell_script' +require 'chef/provider/osx_profile' require 'chef/provider/reboot' require 'chef/provider/remote_directory' require 'chef/provider/remote_file' diff --git a/lib/chef/resource/osx_profile.rb b/lib/chef/resource/osx_profile.rb new file mode 100644 index 0000000000..26b834a9c0 --- /dev/null +++ b/lib/chef/resource/osx_profile.rb @@ -0,0 +1,74 @@ +# +# Author:: Nate Walck () +# Copyright:: Copyright (c) 2015 Facebook, 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 'chef/resource' + +class Chef + class Resource + class OsxProfile < Chef::Resource + provides :osx_profile, os: "darwin" + provides :osx_config_profile, os: "darwin" + + identity_attr :profile_name + + default_action :install + allowed_actions :install, :remove + + def initialize(name, run_context=nil) + super + @profile_name = name + @profile = nil + @identifier = nil + @path = nil + end + + def profile_name(arg=nil) + set_or_return( + :profile_name, + arg, + :kind_of => [ String ] + ) + end + + def profile(arg=nil) + set_or_return( + :profile, + arg, + :kind_of => [ String, Hash ] + ) + end + + def identifier(arg=nil) + set_or_return( + :identifier, + arg, + :kind_of => [ String ] + ) + end + + def path(arg=nil) + set_or_return( + :path, + arg, + :kind_of => [ String ] + ) + end + + end + end +end diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb index 6db0fc9d8d..f699d95ace 100644 --- a/lib/chef/resources.rb +++ b/lib/chef/resources.rb @@ -57,6 +57,7 @@ require 'chef/resource/paludis_package' require 'chef/resource/perl' require 'chef/resource/portage_package' require 'chef/resource/powershell_script' +require 'chef/resource/osx_profile' require 'chef/resource/python' require 'chef/resource/reboot' require 'chef/resource/registry_key' diff --git a/spec/unit/provider/osx_profile_spec.rb b/spec/unit/provider/osx_profile_spec.rb new file mode 100644 index 0000000000..85f9d56e16 --- /dev/null +++ b/spec/unit/provider/osx_profile_spec.rb @@ -0,0 +1,267 @@ +# +# Author:: Nate Walck () +# Copyright:: Copyright (c) 2015 Chef, 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::OsxProfile do + let(:shell_out_success) do + double('shell_out', :exitstatus => 0, :error? => false) + end + describe 'action_create' do + let(:node) { Chef::Node.new } + let(:events) { Chef::EventDispatch::Dispatcher.new } + let(:run_context) { Chef::RunContext.new(node, {}, events) } + let(:new_resource) { Chef::Resource::OsxProfile.new("Profile Test", run_context) } + let(:provider) { Chef::Provider::OsxProfile.new(new_resource, run_context) } + let(:all_profiles) do + {"_computerlevel"=> + [{"ProfileDisplayName"=>"Finder Settings", + "ProfileIdentifier"=>"com.apple.finder", + "ProfileInstallDate"=>"2015-11-08 23:15:21 +0000", + "ProfileItems"=> + [{"PayloadContent"=> + {"PayloadContentManagedPreferences"=> + {"com.apple.finder"=> + {"Forced"=>[{"mcx_preference_settings"=>{"ShowExternalHardDrivesOnDesktop"=>false}}]}}}, + "PayloadDisplayName"=>"Custom: (com.apple.finder)", + "PayloadIdentifier"=>"com.apple.finder", + "PayloadType"=>"com.apple.ManagedClient.preferences", + "PayloadUUID"=>"a017048f-684b-4e81-baa3-43afe316d739", + "PayloadVersion"=>1}], + "ProfileOrganization"=>"Chef", + "ProfileRemovalDisallowed"=>"false", + "ProfileType"=>"Configuration", + "ProfileUUID"=>"e2e09bef-e673-44a6-bcbe-ecb5f1c1b740", + "ProfileVerificationState"=>"unsigned", + "ProfileVersion"=>1}, + {"ProfileDisplayName"=>"ScreenSaver Settings", + "ProfileIdentifier"=>"com.testprofile.screensaver", + "ProfileInstallDate"=>"2015-10-05 23:15:21 +0000", + "ProfileItems"=> + [{"PayloadContent"=> + {"PayloadContentManagedPreferences"=> + {"com.apple.screensaver"=> + {"Forced"=>[{"mcx_preference_settings"=>{"idleTime"=>0}}]}}}, + "PayloadDisplayName"=>"Custom: (com.apple.screensaver)", + "PayloadIdentifier"=>"com.apple.screensaver", + "PayloadType"=>"com.apple.ManagedClient.preferences", + "PayloadUUID"=>"73fc30e0-1e57-0131-c32d-000c2944c110", + "PayloadVersion"=>1}], + "ProfileOrganization"=>"Chef", + "ProfileRemovalDisallowed"=>"false", + "ProfileType"=>"Configuration", + "ProfileUUID"=>"6e95927c-f200-54b4-85c7-52ab99b61c47", + "ProfileVerificationState"=>"unsigned", + "ProfileVersion"=>1}], + } + end + # If anything is changed within this profile, be sure to update the + # ProfileUUID in all_profiles to match the new config specific UUID + let(:test_profile) do + { + 'PayloadIdentifier' => 'com.testprofile.screensaver', + 'PayloadRemovalDisallowed' => false, + 'PayloadScope' => 'System', + 'PayloadType' => 'Configuration', + 'PayloadUUID' => '1781fbec-3325-565f-9022-8aa28135c3cc', + 'PayloadOrganization' => 'Chef', + 'PayloadVersion' => 1, + 'PayloadDisplayName' => 'Screensaver Settings', + 'PayloadContent'=> [ + { + 'PayloadType' => 'com.apple.ManagedClient.preferences', + 'PayloadVersion' => 1, + 'PayloadIdentifier' => 'com.testprofile.screensaver', + 'PayloadUUID' => '73fc30e0-1e57-0131-c32d-000c2944c108', + 'PayloadEnabled' => true, + 'PayloadDisplayName' => 'com.apple.screensaver', + 'PayloadContent' => { + 'com.apple.screensaver' => { + 'Forced' => [ + { + 'mcx_preference_settings' => { + 'idleTime' => 0, + } + } + ] + } + } + } + ] + } + end + let(:no_profiles) do + { "_computerlevel"=> [] } + end + + before(:each) do + allow(provider).to receive(:cookbook_file_available?).and_return(true) + allow(provider).to receive(:cache_cookbook_profile).and_return('/tmp/test.mobileconfig.remote') + allow(provider).to receive(:get_new_profile_hash).and_return(test_profile) + allow(provider).to receive(:get_installed_profiles).and_return(all_profiles) + allow(provider).to receive(:read_plist).and_return(all_profiles) + allow(::File).to receive(:unlink).and_return(true) + end + + it 'should build the get all profiles shellout command correctly' do + profile_name = 'com.testprofile.screensaver.mobileconfig' + tempfile = '/tmp/allprofiles.plist' + new_resource.profile_name profile_name + allow(provider).to receive(:generate_tempfile).and_return(tempfile) + allow(provider).to receive(:get_installed_profiles).and_call_original + allow(provider).to receive(:read_plist).and_return(all_profiles) + expect(provider).to receive(:shell_out!).with("profiles -P -o '/tmp/allprofiles.plist'") + provider.load_current_resource + end + + it 'should use profile name as profile when no profile is set' do + profile_name = 'com.testprofile.screensaver.mobileconfig' + new_resource.profile_name profile_name + provider.load_current_resource + expect(new_resource.profile_name).to eql(profile_name) + end + + it 'should use identifier from specified profile' do + new_resource.profile test_profile + provider.load_current_resource + expect( + provider.instance_variable_get(:@new_profile_identifier) + ).to eql(test_profile['PayloadIdentifier']) + end + + it 'should install when not installed' do + new_resource.profile test_profile + allow(provider).to receive(:get_installed_profiles).and_return(no_profiles) + provider.load_current_resource + expect { provider.run_action(:install) }.to_not raise_error + end + + it 'does not install if the profile is already installed' do + new_resource.profile test_profile + allow(provider).to receive(:get_installed_profiles).and_return(all_profiles) + provider.load_current_resource + expect(provider).to_not receive(:install_profile) + expect { provider.action_install }.to_not raise_error + end + + it 'should install when installed but uuid differs' do + new_resource.profile test_profile + all_profiles['_computerlevel'][1]['ProfileUUID'] = '1781fbec-3325-565f-9022-9bb39245d4dd' + provider.load_current_resource + expect { provider.run_action(:install) }.to_not raise_error + end + + it 'should build the shellout install command correctly' do + profile_path = '/tmp/test.mobileconfig' + new_resource.profile test_profile + # Change the profile so it triggers an install + all_profiles['_computerlevel'][1]['ProfileUUID'] = '1781fbec-3325-565f-9022-9bb39245d4dd' + provider.load_current_resource + allow(provider).to receive(:write_profile_to_disk).and_return(profile_path) + expect(provider).to receive(:shell_out).with("profiles -I -F '#{profile_path}'").and_return(shell_out_success) + provider.action_install() + end + + it 'should fail if there is no identifier inside the profile' do + test_profile.delete('PayloadIdentifier') + new_resource.profile test_profile + error_message = 'The specified profile does not seem to be valid' + expect{provider.run_action(:install)}.to raise_error(RuntimeError, error_message) + end + + end + + describe 'action_remove' do + let(:node) { Chef::Node.new } + let(:events) { Chef::EventDispatch::Dispatcher.new } + let(:run_context) { Chef::RunContext.new(node, {}, events) } + let(:new_resource) { Chef::Resource::OsxProfile.new("Profile Test", run_context) } + let(:provider) { Chef::Provider::OsxProfile.new(new_resource, run_context) } + let(:current_resource) { Chef::Resource::OsxProfile.new('Profile Test') } + let(:all_profiles) do + {"_computerlevel"=> + [{"ProfileDisplayName"=>"ScreenSaver Settings", + "ProfileIdentifier"=>"com.apple.screensaver", + "ProfileInstallDate"=>"2015-10-05 23:15:21 +0000", + "ProfileItems"=> + [{"PayloadContent"=> + {"PayloadContentManagedPreferences"=> + {"com.apple.screensaver"=> + {"Forced"=>[{"mcx_preference_settings"=>{"idleTime"=>0}}]}}}, + "PayloadDisplayName"=>"Custom: (com.apple.screensaver)", + "PayloadIdentifier"=>"com.apple.screensaver", + "PayloadType"=>"com.apple.ManagedClient.preferences", + "PayloadUUID"=>"73fc30e0-1e57-0131-c32d-000c2944c108", + "PayloadVersion"=>1}], + "ProfileOrganization"=>"Chef", + "ProfileRemovalDisallowed"=>"false", + "ProfileType"=>"Configuration", + "ProfileUUID"=>"1781fbec-3325-565f-9022-8aa28135c3cc", + "ProfileVerificationState"=>"unsigned", + "ProfileVersion"=>1}, + {"ProfileDisplayName"=>"ScreenSaver Settings", + "ProfileIdentifier"=>"com.testprofile.screensaver", + "ProfileInstallDate"=>"2015-10-05 23:15:21 +0000", + "ProfileItems"=> + [{"PayloadContent"=> + {"PayloadContentManagedPreferences"=> + {"com.apple.screensaver"=> + {"Forced"=>[{"mcx_preference_settings"=>{"idleTime"=>0}}]}}}, + "PayloadDisplayName"=>"Custom: (com.apple.screensaver)", + "PayloadIdentifier"=>"com.apple.screensaver", + "PayloadType"=>"com.apple.ManagedClient.preferences", + "PayloadUUID"=>"73fc30e0-1e57-0131-c32d-000c2944c110", + "PayloadVersion"=>1}], + "ProfileOrganization"=>"Chef", + "ProfileRemovalDisallowed"=>"false", + "ProfileType"=>"Configuration", + "ProfileUUID"=>"1781fbec-3325-565f-9022-8aa28135c3cc", + "ProfileVerificationState"=>"unsigned", + "ProfileVersion"=>1}], + } + end + before(:each) do + provider.current_resource = current_resource + allow(provider).to receive(:get_installed_profiles).and_return(all_profiles) + end + + it 'should use resource name for identifier when not specified' do + new_resource.profile_name 'com.testprofile.screensaver' + new_resource.action(:remove) + provider.load_current_resource + expect(provider.instance_variable_get(:@new_profile_identifier) + ).to eql(new_resource.profile_name) + end + + it 'should use specified identifier' do + new_resource.identifier 'com.testprofile.screensaver' + new_resource.action(:remove) + provider.load_current_resource + expect(provider.instance_variable_get(:@new_profile_identifier) + ).to eql(new_resource.identifier) + end + + it 'should build the shellout remove command correctly' do + new_resource.identifier 'com.testprofile.screensaver' + new_resource.action(:remove) + provider.load_current_resource + expect(provider).to receive(:shell_out).with("profiles -R -p '#{new_resource.identifier}'").and_return(shell_out_success) + provider.action_remove() + end + end +end diff --git a/spec/unit/provider_resolver_spec.rb b/spec/unit/provider_resolver_spec.rb index c1256180af..8c087cf3f3 100644 --- a/spec/unit/provider_resolver_spec.rb +++ b/spec/unit/provider_resolver_spec.rb @@ -708,6 +708,7 @@ describe Chef::ProviderResolver do %w(mac_os_x mac_os_x_server) => { group: [ Chef::Resource::Group, Chef::Provider::Group::Dscl ], package: [ Chef::Resource::HomebrewPackage, Chef::Provider::Package::Homebrew ], + osx_profile: [ Chef::Resource::OsxProfile, Chef::Provider::OsxProfile], user: [ Chef::Resource::User, Chef::Provider::User::Dscl ], "mac_os_x" => { diff --git a/spec/unit/resource/osx_profile_spec.rb b/spec/unit/resource/osx_profile_spec.rb new file mode 100644 index 0000000000..d7d72e5836 --- /dev/null +++ b/spec/unit/resource/osx_profile_spec.rb @@ -0,0 +1,61 @@ +# +# Author:: Nate Walck () +# Copyright:: Copyright (c) 2015 Facebook, 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::Resource::OsxProfile do + let(:resource) { Chef::Resource::OsxProfile.new( + "Test Profile Resource", + run_context) + } + + it "should create a new Chef::Resource::OsxProfile" do + expect(resource).to be_a_kind_of(Chef::Resource) + expect(resource).to be_a_kind_of(Chef::Resource::OsxProfile) + end + + it "should have a resource name of profile" do + expect(resource.resource_name).to eql(:osx_profile) + end + + it "should have a default action of install" do + expect(resource.action).to eql([:install]) + end + + it "should accept install and remove as actions" do + expect { resource.action :install}.not_to raise_error + expect { resource.action :remove}.not_to raise_error + end + + it "should allow you to set the profile attribute" do + resource.profile "com.testprofile.screensaver" + expect(resource.profile).to eql("com.testprofile.screensaver") + end + + it "should allow you to set the profile attribute to a string" do + resource.profile "com.testprofile.screensaver" + expect(resource.profile).to be_a(String) + expect(resource.profile).to eql("com.testprofile.screensaver") + end + + it "should allow you to set the profile attribute to a hash" do + test_profile = { 'profile' => false } + resource.profile test_profile + expect(resource.profile).to be_a(Hash) + end +end -- cgit v1.2.1