diff options
122 files changed, 1554 insertions, 810 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 48cd1d2cc4..5b4bdcd9c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,7 +74,12 @@ Improve the regex for /etc/rc.conf for the FreeBSD service provider * [**Stanislav Bogatyrev**](https://github.com/realloc): Fetch recipe_url before loading json_attribs in chef-solo (CHEF-5075) -* [**Mal Graty**](https://github.com/mal): Workaround for a breaking change in git's shallow-clone behavior. (Issue 1563) +* [**Mal Graty**](https://github.com/mal): + Workaround for a breaking change in git's shallow-clone behavior. (Issue 1563) +* [**Dave Eddy**](https://github.com/bahamas10): + Fix version detection in FreeBSD pkgng provider. (PR 1980) +* [**Dan Rathbone**](https://github.com/rathers): + Fixed gem_package resource to be able to upgrade gems when version is not set. ### Chef Contributions @@ -145,6 +150,9 @@ * Add escape_glob method to PathHelper, update glob operations. * Verify x509 properties of certificates in the :trusted_certs_dir during knife ssl check. * Disable unforked interval chef-client runs. +* Removed dependencies on the 'json' gem, replaced with ffi-yajl. Use Chef::JSONCompat library for parsing and printing. +* Restore the deprecation logic of #valid_actions in LWRPs until Chef 13. +* Now that we don't allow unforked chef-client interval runs, remove the reloading of previously defined LWRPs. ## Last Release: 11.14.2 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e6e9a26cad..f8618ad381 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -159,6 +159,15 @@ In order to decrease the back and forth an issues and help us get to the bottom [What actually happens after the reproduction steps?] ``` +### Useful Github Queries + +Contributions go through a review process to improve code quality and avoid regressions. Managing a large number of contributions requires a workflow to provide queues for work such as triage, code review, and merging. A semi-formal process has evolved over the life of the project. Chef maintains this process pending community development and acceptance of an [RFC](https://github.com/opscode/chef-rfc). These queries will help track contributions through this process: + +* [Issues that are not assigned to a team](https://github.com/opscode/chef/issues?q=is%3Aopen+-label%3AAIX+-label%3ABSD+-label%3Awindows+-label%3A%22Chef+Core%22++-label%3A%22Dev+Tools%22+-label%3AUbuntu+-label%3A%22Enterprise+Linux%22+-label%3A%22Ready+For+Merge%22+-label%3AMac+-label%3ASolaris+) +* [Untriaged Issues](https://github.com/opscode/chef/issues?q=is%3Aopen+is%3Aissue+-label%3ABug+-label%3AEnhancement+-label%3A%22Tech+Cleanup%22+-label%3A%22Ready+For+Merge%22) +* [PRs to be Reviewed](https://github.com/opscode/chef/labels/Pending%20Maintainer%20Review) +* [Suitable for First Contribution](https://github.com/opscode/chef/labels/Easy) + ## <a name="release"></a> Chef Release Cycles Our primary shipping vehicle is operating system specific packages that includes diff --git a/DOC_CHANGES.md b/DOC_CHANGES.md index ceccb77cd0..08c343809a 100644 --- a/DOC_CHANGES.md +++ b/DOC_CHANGES.md @@ -467,7 +467,6 @@ PathHelper = Chef::Util::PathHelper Dir.glob(File.join(PathHelper.escape_glob(path), "*")) # ["#{path}\\apache2", "#{path}\\apt", ...] Dir[PathHelper.escape_glob(path) + "/*"] # ["#{path}\\apache2", "#{path}\\apt", ...] ``` - ## Mac OS X default package provider is now Homebrew Per [Chef RFC 016](https://github.com/opscode/chef-rfc/blob/master/rfc016-homebrew-osx-package-provider.md), the default provider for the `package` resource on Mac OS X is now [Homebrew](http://brew.sh). The [homebrew cookbook's](https://supermarket.getchef.com/cookbooks/homebrew) default recipe, or some other method is still required for getting homebrew installed on the system. The cookbook won't be strictly required just to install packages from homebrew on OS X, though. To use this, simply use the `package` resource, or the `homebrew_package` shortcut resource: @@ -521,4 +520,4 @@ end ``` Chef will then execute the Homebrew command as that user. The `homebrew_user` attribute can only be provided to the -`homebrew_package` resource, not the `package` resource.
\ No newline at end of file +`homebrew_package` resource, not the `package` resource. diff --git a/chef.gemspec b/chef.gemspec index 481b120fe6..075d1fc5d6 100644 --- a/chef.gemspec +++ b/chef.gemspec @@ -22,7 +22,7 @@ Gem::Specification.new do |s| s.add_dependency "mixlib-shellout", ">= 2.0.0.rc.0", "< 3.0" s.add_dependency "ohai", ">= 7.6.0.rc.0" - s.add_dependency "ffi-yajl", "~> 1.0", ">= 1.0.2" + s.add_dependency "ffi-yajl", "~> 1.2" s.add_dependency "net-ssh", "~> 2.6" s.add_dependency "net-ssh-multi", "~> 1.1" # CHEF-3027: The knife-cloud plugins require newer features from highline, core chef should not. @@ -30,7 +30,7 @@ Gem::Specification.new do |s| s.add_dependency "erubis", "~> 2.7" s.add_dependency "diff-lcs", "~> 1.2", ">= 1.2.4" - s.add_dependency "chef-zero", "~> 3.1" + s.add_dependency "chef-zero", "~> 3.2" s.add_dependency "pry", "~> 0.9" s.add_dependency 'plist', '~> 3.1.0' diff --git a/kitchen-tests/test/fixtures/serverspec_helper.rb b/kitchen-tests/test/fixtures/serverspec_helper.rb index 6e6d71e79b..48963dc45d 100644 --- a/kitchen-tests/test/fixtures/serverspec_helper.rb +++ b/kitchen-tests/test/fixtures/serverspec_helper.rb @@ -4,6 +4,7 @@ require 'serverspec' require 'json' +require 'ffi_yajl' set :backend, :exec @@ -21,7 +22,7 @@ def load_nodestub platform = 'centos' platform_version = os[:release].to_i end - JSON.parse(IO.read("#{ENV['BUSSER_ROOT']}/../kitchen/data/platforms/#{platform}/#{platform_version}.json"), :symbolize_names => true) + FFI_Yajl::Parser.parse(IO.read("#{ENV['BUSSER_ROOT']}/../kitchen/data/platforms/#{platform}/#{platform_version}.json"), :symbolize_names => true) end # centos-59 doesn't have /sbin in the default path, diff --git a/kitchen-tests/test/integration/webapp/serverspec/Gemfile b/kitchen-tests/test/integration/webapp/serverspec/Gemfile new file mode 100644 index 0000000000..0cb00ce354 --- /dev/null +++ b/kitchen-tests/test/integration/webapp/serverspec/Gemfile @@ -0,0 +1,4 @@ +# This Gemfile is only needed so that busser will install gems it needs for serverspec_helper.rb to work +source "https://rubygems.org" + +gem 'ffi-yajl', '~> 1.1' # Go away, JSON gem diff --git a/lib/chef/chef_fs/chef_fs_data_store.rb b/lib/chef/chef_fs/chef_fs_data_store.rb index 484ab07390..3813d0edb4 100644 --- a/lib/chef/chef_fs/chef_fs_data_store.rb +++ b/lib/chef/chef_fs/chef_fs_data_store.rb @@ -168,7 +168,7 @@ class Chef end end end - JSON.pretty_generate(result) + Chef::JSONCompat.to_json_pretty(result) else begin diff --git a/lib/chef/chef_fs/command_line.rb b/lib/chef/chef_fs/command_line.rb index 43e8b276e0..8a205eef78 100644 --- a/lib/chef/chef_fs/command_line.rb +++ b/lib/chef/chef_fs/command_line.rb @@ -253,7 +253,7 @@ class Chef def self.canonicalize_json(json_text) parsed_json = Chef::JSONCompat.parse(json_text) sorted_json = sort_keys(parsed_json) - JSON.pretty_generate(sorted_json) + Chef::JSONCompat.to_json_pretty(sorted_json) end def self.diff_text(old_path, new_path, old_value, new_value) diff --git a/lib/chef/chef_fs/file_system/organization_invites_entry.rb b/lib/chef/chef_fs/file_system/organization_invites_entry.rb index cb26326050..5df37085cb 100644 --- a/lib/chef/chef_fs/file_system/organization_invites_entry.rb +++ b/lib/chef/chef_fs/file_system/organization_invites_entry.rb @@ -1,5 +1,6 @@ require 'chef/chef_fs/file_system/rest_list_entry' require 'chef/chef_fs/data_handler/organization_invites_data_handler' +require 'chef/json_compat' class Chef module ChefFS @@ -34,7 +35,7 @@ class Chef end def write(contents) - desired_invites = minimize_value(JSON.parse(contents, :create_additions => false)) + desired_invites = minimize_value(Chef::JSONCompat.parse(contents, :create_additions => false)) actual_invites = _read_json.inject({}) { |h,val| h[val['username']] = val['id']; h } invites = actual_invites.keys (desired_invites - invites).each do |invite| diff --git a/lib/chef/chef_fs/file_system/organization_members_entry.rb b/lib/chef/chef_fs/file_system/organization_members_entry.rb index eb524d5ea2..94393b341f 100644 --- a/lib/chef/chef_fs/file_system/organization_members_entry.rb +++ b/lib/chef/chef_fs/file_system/organization_members_entry.rb @@ -1,5 +1,6 @@ require 'chef/chef_fs/file_system/rest_list_entry' require 'chef/chef_fs/data_handler/organization_members_data_handler' +require 'chef/json_compat' class Chef module ChefFS @@ -34,7 +35,7 @@ class Chef end def write(contents) - desired_members = minimize_value(JSON.parse(contents, :create_additions => false)) + desired_members = minimize_value(Chef::JSONCompat.parse(contents, :create_additions => false)) members = minimize_value(_read_json) (desired_members - members).each do |member| begin diff --git a/lib/chef/chef_fs/file_system/rest_list_entry.rb b/lib/chef/chef_fs/file_system/rest_list_entry.rb index ac47ff4f25..f68794cb0d 100644 --- a/lib/chef/chef_fs/file_system/rest_list_entry.rb +++ b/lib/chef/chef_fs/file_system/rest_list_entry.rb @@ -21,6 +21,7 @@ require 'chef/chef_fs/file_system/not_found_error' require 'chef/chef_fs/file_system/operation_failed_error' require 'chef/role' require 'chef/node' +require 'chef/json_compat' class Chef module ChefFS @@ -172,7 +173,7 @@ class Chef def api_error_text(response) begin - JSON.parse(response.body)['error'].join("\n") + Chef::JSONCompat.parse(response.body)['error'].join("\n") rescue response.body end diff --git a/lib/chef/client.rb b/lib/chef/client.rb index e531da5768..e8ff352116 100644 --- a/lib/chef/client.rb +++ b/lib/chef/client.rb @@ -282,7 +282,7 @@ class Chef rescue Exception => e # TODO: munge exception so a semantic failure message can be given to the # user - @events.registration_failed(node_name, e, config) + @events.registration_failed(client_name, e, config) raise end diff --git a/lib/chef/cookbook/cookbook_version_loader.rb b/lib/chef/cookbook/cookbook_version_loader.rb index 5481ba7ddc..bcbfcbeec8 100644 --- a/lib/chef/cookbook/cookbook_version_loader.rb +++ b/lib/chef/cookbook/cookbook_version_loader.rb @@ -81,7 +81,7 @@ class Chef load_as(:attribute_filenames, 'attributes', '*.rb') load_as(:definition_filenames, 'definitions', '*.rb') load_as(:recipe_filenames, 'recipes', '*.rb') - load_as(:library_filenames, 'libraries', '*.rb') + load_recursively_as(:library_filenames, 'libraries', '*.rb') load_recursively_as(:template_filenames, "templates", "*") load_recursively_as(:file_filenames, "files", "*") load_recursively_as(:resource_filenames, "resources", "*.rb") diff --git a/lib/chef/cookbook/metadata.rb b/lib/chef/cookbook/metadata.rb index 3964354d50..54e930135d 100644 --- a/lib/chef/cookbook/metadata.rb +++ b/lib/chef/cookbook/metadata.rb @@ -35,28 +35,31 @@ class Chef # about Chef Cookbooks. class Metadata - NAME = 'name'.freeze - DESCRIPTION = 'description'.freeze - LONG_DESCRIPTION = 'long_description'.freeze - MAINTAINER = 'maintainer'.freeze - MAINTAINER_EMAIL = 'maintainer_email'.freeze - LICENSE = 'license'.freeze - PLATFORMS = 'platforms'.freeze - DEPENDENCIES = 'dependencies'.freeze - RECOMMENDATIONS = 'recommendations'.freeze - SUGGESTIONS = 'suggestions'.freeze - CONFLICTING = 'conflicting'.freeze - PROVIDING = 'providing'.freeze - REPLACING = 'replacing'.freeze - ATTRIBUTES = 'attributes'.freeze - GROUPINGS = 'groupings'.freeze - RECIPES = 'recipes'.freeze - VERSION = 'version'.freeze + NAME = 'name'.freeze + DESCRIPTION = 'description'.freeze + LONG_DESCRIPTION = 'long_description'.freeze + MAINTAINER = 'maintainer'.freeze + MAINTAINER_EMAIL = 'maintainer_email'.freeze + LICENSE = 'license'.freeze + PLATFORMS = 'platforms'.freeze + DEPENDENCIES = 'dependencies'.freeze + RECOMMENDATIONS = 'recommendations'.freeze + SUGGESTIONS = 'suggestions'.freeze + CONFLICTING = 'conflicting'.freeze + PROVIDING = 'providing'.freeze + REPLACING = 'replacing'.freeze + ATTRIBUTES = 'attributes'.freeze + GROUPINGS = 'groupings'.freeze + RECIPES = 'recipes'.freeze + VERSION = 'version'.freeze + SOURCE_URL = 'source_url'.freeze + ISSUES_URL = 'issues_url'.freeze COMPARISON_FIELDS = [ :name, :description, :long_description, :maintainer, :maintainer_email, :license, :platforms, :dependencies, :recommendations, :suggestions, :conflicting, :providing, - :replacing, :attributes, :groupings, :recipes, :version] + :replacing, :attributes, :groupings, :recipes, :version, + :source_url, :issues_url ] VERSION_CONSTRAINTS = {:depends => DEPENDENCIES, :recommends => RECOMMENDATIONS, @@ -111,6 +114,8 @@ class Chef @groupings = Mash.new @recipes = Mash.new @version = Version.new("0.0.0") + @source_url = '' + @issues_url = '' @errors = [] end @@ -413,7 +418,7 @@ class Chef end end - # Adds an attribute )hat a user needs to configure for this cookbook. Takes + # Adds an attribute that a user needs to configure for this cookbook. Takes # a name (with the / notation for a nested attribute), followed by any of # these options # @@ -443,7 +448,9 @@ class Chef :type => { :equal_to => [ "string", "array", "hash", "symbol", "boolean", "numeric" ], :default => "string" }, :required => { :equal_to => [ "required", "recommended", "optional", true, false ], :default => "optional" }, :recipes => { :kind_of => [ Array ], :default => [] }, - :default => { :kind_of => [ String, Array, Hash, Symbol, Numeric, TrueClass, FalseClass ] } + :default => { :kind_of => [ String, Array, Hash, Symbol, Numeric, TrueClass, FalseClass ] }, + :source_url => { :kind_of => String }, + :issues_url => { :kind_of => String } } ) options[:required] = remap_required_attribute(options[:required]) unless options[:required].nil? @@ -469,23 +476,25 @@ class Chef def to_hash { - NAME => self.name, - DESCRIPTION => self.description, - LONG_DESCRIPTION => self.long_description, - MAINTAINER => self.maintainer, - MAINTAINER_EMAIL => self.maintainer_email, - LICENSE => self.license, - PLATFORMS => self.platforms, - DEPENDENCIES => self.dependencies, - RECOMMENDATIONS => self.recommendations, - SUGGESTIONS => self.suggestions, - CONFLICTING => self.conflicting, - PROVIDING => self.providing, - REPLACING => self.replacing, - ATTRIBUTES => self.attributes, - GROUPINGS => self.groupings, - RECIPES => self.recipes, - VERSION => self.version + NAME => self.name, + DESCRIPTION => self.description, + LONG_DESCRIPTION => self.long_description, + MAINTAINER => self.maintainer, + MAINTAINER_EMAIL => self.maintainer_email, + LICENSE => self.license, + PLATFORMS => self.platforms, + DEPENDENCIES => self.dependencies, + RECOMMENDATIONS => self.recommendations, + SUGGESTIONS => self.suggestions, + CONFLICTING => self.conflicting, + PROVIDING => self.providing, + REPLACING => self.replacing, + ATTRIBUTES => self.attributes, + GROUPINGS => self.groupings, + RECIPES => self.recipes, + VERSION => self.version, + SOURCE_URL => self.source_url, + ISSUES_URL => self.issues_url } end @@ -517,6 +526,8 @@ class Chef @groupings = o[GROUPINGS] if o.has_key?(GROUPINGS) @recipes = o[RECIPES] if o.has_key?(RECIPES) @version = o[VERSION] if o.has_key?(VERSION) + @source_url = o[SOURCE_URL] if o.has_key?(SOURCE_URL) + @issues_url = o[ISSUES_URL] if o.has_key?(ISSUES_URL) self end @@ -531,7 +542,9 @@ class Chef VERSION_CONSTRAINTS.each do |dependency_type, hash_key| if dependency_group = o[hash_key] dependency_group.each do |cb_name, constraints| - metadata.send(method_name, cb_name, *Array(constraints)) + if metadata.respond_to?(method_name) + metadata.public_send(method_name, cb_name, *Array(constraints)) + end end end end @@ -543,6 +556,36 @@ class Chef from_hash(o) end + # Sets the cookbook's source URL, or returns it. + # + # === Parameters + # maintainer<String>:: The source URL + # + # === Returns + # source_url<String>:: Returns the current source URL. + def source_url(arg=nil) + set_or_return( + :source_url, + arg, + :kind_of => [ String ] + ) + end + + # Sets the cookbook's issues URL, or returns it. + # + # === Parameters + # issues_url<String>:: The issues URL + # + # === Returns + # issues_url<String>:: Returns the current issues URL. + def issues_url(arg=nil) + set_or_return( + :issues_url, + arg, + :kind_of => [ String ] + ) + end + private def run_validation diff --git a/lib/chef/cookbook/syntax_check.rb b/lib/chef/cookbook/syntax_check.rb index 1437785259..96fc7e7b84 100644 --- a/lib/chef/cookbook/syntax_check.rb +++ b/lib/chef/cookbook/syntax_check.rb @@ -101,15 +101,24 @@ class Chef end def remove_ignored_files(file_list) - return file_list unless chefignore.ignores.length > 0 + return file_list if chefignore.ignores.empty? + file_list.reject do |full_path| relative_pn = Chef::Util::PathHelper.relative_path_from(cookbook_path, full_path) - chefignore.ignored? relative_pn.to_s + chefignore.ignored?(relative_pn.to_s) end end + def remove_uninteresting_ruby_files(file_list) + file_list.reject { |f| f =~ %r{#{cookbook_path}/(files|templates)/} } + end + def ruby_files - remove_ignored_files Dir[File.join(Chef::Util::PathHelper.escape_glob(cookbook_path), '**', '*.rb')] + path = Chef::Util::PathHelper.escape_glob(cookbook_path) + files = Dir[File.join(path, '**', '*.rb')] + files = remove_ignored_files(files) + files = remove_uninteresting_ruby_files(files) + files end def untested_ruby_files diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb index 40aaaf1191..505b403e65 100644 --- a/lib/chef/cookbook_version.rb +++ b/lib/chef/cookbook_version.rb @@ -315,13 +315,20 @@ class Chef else if segment == :files || segment == :templates error_message = "Cookbook '#{name}' (#{version}) does not contain a file at any of these locations:\n" - error_locations = [ - " #{segment}/#{node[:platform]}-#{node[:platform_version]}/#{filename}", - " #{segment}/#{node[:platform]}/#{filename}", - " #{segment}/default/#{filename}", - ] + error_locations = if filename.is_a?(Array) + filename.map{|name| " #{File.join(segment.to_s, name)}"} + else + [ + " #{segment}/#{node[:platform]}-#{node[:platform_version]}/#{filename}", + " #{segment}/#{node[:platform]}/#{filename}", + " #{segment}/default/#{filename}", + " #{segment}/#{filename}", + ] + end error_message << error_locations.join("\n") existing_files = segment_filenames(segment) + # Strip the root_dir prefix off all files for readability + existing_files.map!{|path| path[root_dir.length+1..-1]} if root_dir # Show the files that the cookbook does have. If the user made a typo, # hopefully they'll see it here. unless existing_files.empty? @@ -421,38 +428,44 @@ class Chef def preferences_for_path(node, segment, path) # only files and templates can be platform-specific if segment.to_sym == :files || segment.to_sym == :templates - begin - platform, version = Chef::Platform.find_platform_and_version(node) - rescue ArgumentError => e - # Skip platform/version if they were not found by find_platform_and_version - if e.message =~ /Cannot find a (?:platform|version)/ - platform = "/unknown_platform/" - version = "/unknown_platform_version/" - else - raise + relative_search_path = if path.is_a?(Array) + path + else + begin + platform, version = Chef::Platform.find_platform_and_version(node) + rescue ArgumentError => e + # Skip platform/version if they were not found by find_platform_and_version + if e.message =~ /Cannot find a (?:platform|version)/ + platform = "/unknown_platform/" + version = "/unknown_platform_version/" + else + raise + end end - end - fqdn = node[:fqdn] + fqdn = node[:fqdn] - # Break version into components, eg: "5.7.1" => [ "5.7.1", "5.7", "5" ] - search_versions = [] - parts = version.to_s.split('.') + # Break version into components, eg: "5.7.1" => [ "5.7.1", "5.7", "5" ] + search_versions = [] + parts = version.to_s.split('.') - parts.size.times do - search_versions << parts.join('.') - parts.pop - end + parts.size.times do + search_versions << parts.join('.') + parts.pop + end - # Most specific to least specific places to find the path - search_path = [ File.join(segment.to_s, "host-#{fqdn}", path) ] - search_versions.each do |v| - search_path << File.join(segment.to_s, "#{platform}-#{v}", path) - end - search_path << File.join(segment.to_s, platform.to_s, path) - search_path << File.join(segment.to_s, "default", path) + # Most specific to least specific places to find the path + search_path = [ File.join("host-#{fqdn}", path) ] + search_versions.each do |v| + search_path << File.join("#{platform}-#{v}", path) + end + search_path << File.join(platform.to_s, path) + search_path << File.join("default", path) + search_path << path - search_path + search_path + end + relative_search_path.map {|relative_path| File.join(segment.to_s, relative_path)} else [File.join(segment, path)] end @@ -479,7 +492,7 @@ class Chef cookbook_version.manifest = o # We don't need the following step when we decide to stop supporting deprecated operators in the metadata (e.g. <<, >>) - cookbook_version.manifest["metadata"] = Chef::JSONCompat.from_json(cookbook_version.metadata.to_json) + cookbook_version.manifest["metadata"] = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(cookbook_version.metadata)) cookbook_version.freeze_version if o["frozen?"] cookbook_version @@ -666,7 +679,13 @@ class Chef # Check if path is actually under root_path next if parts[0] == '..' if segment == :templates || segment == :files - return [ pathname.to_s, parts[1] ] + # Check if pathname looks like files/foo or templates/foo (unscoped) + if pathname.each_filename.to_a.length == 2 + # Use root_default in case the same path exists at root_default and default + return [ pathname.to_s, 'root_default' ] + else + return [ pathname.to_s, parts[1] ] + end else return [ pathname.to_s, 'default' ] end diff --git a/lib/chef/dsl/recipe.rb b/lib/chef/dsl/recipe.rb index 3282320b8c..476d9db666 100644 --- a/lib/chef/dsl/recipe.rb +++ b/lib/chef/dsl/recipe.rb @@ -19,6 +19,7 @@ require 'chef/resource_platform_map' require 'chef/mixin/convert_to_class_name' +require 'chef/exceptions' class Chef module DSL @@ -162,6 +163,10 @@ class Chef end end + def exec(args) + raise Chef::Exceptions::ResourceNotFound, "exec was called, but you probably meant to use an execute resource. If not, please call Kernel#exec explicitly. The exec block called was \"#{args}\"" + end + end end end diff --git a/lib/chef/http.rb b/lib/chef/http.rb index 7f2d00157b..ee951bd675 100644 --- a/lib/chef/http.rb +++ b/lib/chef/http.rb @@ -271,7 +271,7 @@ class Chef elsif redirect_location = redirected_to(response) if [:GET, :HEAD].include?(method) follow_redirect do - send_http_request(method, create_url(redirect_location), headers, body, &response_handler) + send_http_request(method, url+redirect_location, headers, body, &response_handler) end else raise Exceptions::InvalidRedirect, "#{method} request was redirected from #{url} to #{redirect_location}. Only GET and HEAD support redirects." diff --git a/lib/chef/json_compat.rb b/lib/chef/json_compat.rb index e92d5c36ae..3350da0c13 100644 --- a/lib/chef/json_compat.rb +++ b/lib/chef/json_compat.rb @@ -18,9 +18,9 @@ # Wrapper class for interacting with JSON. require 'ffi_yajl' -require 'json' -require 'ffi_yajl/json_gem' # XXX: parts of chef require JSON gem's Hash#to_json monkeypatch require 'chef/exceptions' +# We're requiring this to prevent breaking consumers using Hash.to_json +require 'json' class Chef class JSONCompat diff --git a/lib/chef/knife.rb b/lib/chef/knife.rb index 6421384f01..0c079792a4 100644 --- a/lib/chef/knife.rb +++ b/lib/chef/knife.rb @@ -164,7 +164,6 @@ class Chef def self.load_config(explicit_config_file) config_loader = WorkstationConfigLoader.new(explicit_config_file, Chef::Log) - Chef::Log.debug("Using configuration from #{config_loader.config_location}") config_loader.load ui.warn("No knife configuration file found") if config_loader.no_config_found? @@ -393,6 +392,8 @@ class Chef merge_configs apply_computed_config + # This has to be after apply_computed_config so that Mixlib::Log is configured + Chef::Log.info("Using configuration from #{config[:config_file]}") if config[:config_file] end def show_usage diff --git a/lib/chef/knife/bootstrap/archlinux-gems.erb b/lib/chef/knife/bootstrap/archlinux-gems.erb index eb134b90d5..581293daa3 100644 --- a/lib/chef/knife/bootstrap/archlinux-gems.erb +++ b/lib/chef/knife/bootstrap/archlinux-gems.erb @@ -34,7 +34,7 @@ mkdir -p /etc/chef/ohai/hints <% @chef_config[:knife][:hints].each do |name, hash| -%> cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP' -<%= hash.to_json %> +<%= Chef::JSONCompat.to_json(hash) %> EOP <% end -%> <% end -%> @@ -61,7 +61,7 @@ https_proxy "<%= knife_config[:bootstrap_proxy] %>" EOP cat > /etc/chef/first-boot.json <<'EOP' -<%= first_boot.to_json %> +<%= Chef::JSONCompat.to_json(first_boot) %> EOP <%= start_chef %>' diff --git a/lib/chef/knife/bootstrap/chef-aix.erb b/lib/chef/knife/bootstrap/chef-aix.erb index 3a031ee738..013ad1decb 100644 --- a/lib/chef/knife/bootstrap/chef-aix.erb +++ b/lib/chef/knife/bootstrap/chef-aix.erb @@ -47,7 +47,7 @@ mkdir -p /etc/chef/ohai/hints <% @chef_config[:knife][:hints].each do |name, hash| -%> cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP' -<%= hash.to_json %> +<%= Chef::JSONCompat.to_json(hash) %> EOP <% end -%> <% end -%> @@ -57,7 +57,7 @@ cat > /etc/chef/client.rb <<'EOP' EOP cat > /etc/chef/first-boot.json <<'EOP' -<%= first_boot.to_json %> +<%= Chef::JSONCompat.to_json(first_boot) %> EOP <%= start_chef %>' diff --git a/lib/chef/knife/bootstrap/chef-full.erb b/lib/chef/knife/bootstrap/chef-full.erb index 6edb485f44..dfd5df0071 100644 --- a/lib/chef/knife/bootstrap/chef-full.erb +++ b/lib/chef/knife/bootstrap/chef-full.erb @@ -61,7 +61,7 @@ mkdir -p /etc/chef/ohai/hints <% @chef_config[:knife][:hints].each do |name, hash| -%> cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP' -<%= hash.to_json %> +<%= Chef::JSONCompat.to_json(hash) %> EOP <% end -%> <% end -%> @@ -71,7 +71,7 @@ cat > /etc/chef/client.rb <<'EOP' EOP cat > /etc/chef/first-boot.json <<'EOP' -<%= first_boot.to_json %> +<%= Chef::JSONCompat.to_json(first_boot) %> EOP echo "Starting first Chef Client run..." diff --git a/lib/chef/knife/cookbook_create.rb b/lib/chef/knife/cookbook_create.rb index f4183a7245..e17a54079f 100644 --- a/lib/chef/knife/cookbook_create.rb +++ b/lib/chef/knife/cookbook_create.rb @@ -80,7 +80,7 @@ class Chef end def create_cookbook(dir, cookbook_name, copyright, license) - msg("** Creating cookbook #{cookbook_name}") + msg("** Creating cookbook #{cookbook_name} in #{dir}") FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "attributes")}" FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "recipes")}" FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "definitions")}" diff --git a/lib/chef/knife/cookbook_site_share.rb b/lib/chef/knife/cookbook_site_share.rb index 7204ccdb1c..b4a7873c71 100644 --- a/lib/chef/knife/cookbook_site_share.rb +++ b/lib/chef/knife/cookbook_site_share.rb @@ -31,7 +31,7 @@ class Chef require 'chef/cookbook_site_streaming_uploader' end - banner "knife cookbook site share COOKBOOK CATEGORY (options)" + banner "knife cookbook site share COOKBOOK [CATEGORY] (options)" category "cookbook site" option :cookbook_path, @@ -40,17 +40,28 @@ class Chef :description => "A colon-separated path to look for cookbooks in", :proc => lambda { |o| Chef::Config.cookbook_path = o.split(":") } + option :dry_run, + :long => '--dry-run', + :short => '-n', + :boolean => true, + :default => false, + :description => "Don't take action, only print what files will be upload to SuperMarket." + def run - if @name_args.length < 2 + config[:cookbook_path] ||= Chef::Config[:cookbook_path] + + if @name_args.length < 1 show_usage - ui.fatal("You must specify the cookbook name and the category you want to share this cookbook to.") - exit 1 + ui.fatal("You must specify the cookbook name.") + exit(1) + elsif @name_args.length < 2 + cookbook_name = @name_args[0] + category = get_category(cookbook_name) + else + cookbook_name = @name_args[0] + category = @name_args[1] end - config[:cookbook_path] ||= Chef::Config[:cookbook_path] - - cookbook_name = @name_args[0] - category = @name_args[1] cl = Chef::CookbookLoader.new(config[:cookbook_path]) if cl.cookbook_exists?(cookbook_name) cookbook = cl[cookbook_name] @@ -66,6 +77,14 @@ class Chef exit(1) end + if config[:dry_run] + ui.info("Not uploading #{cookbook_name}.tgz due to --dry-run flag.") + result = shell_out!("tar -tzf #{cookbook_name}.tgz", :cwd => tmp_cookbook_dir) + ui.info(result.stdout) + FileUtils.rm_rf tmp_cookbook_dir + return + end + begin do_upload("#{tmp_cookbook_dir}/#{cookbook_name}.tgz", category, Chef::Config[:node_name], Chef::Config[:client_key]) ui.info("Upload complete!") @@ -84,6 +103,22 @@ class Chef end + def get_category(cookbook_name) + begin + data = noauth_rest.get_rest("http://cookbooks.opscode.com/api/v1/cookbooks/#{@name_args[0]}") + if !data["category"] && data["error_code"] + ui.fatal("Received an error from the Opscode Cookbook site: #{data["error_code"]}. On the first time you upload it, you are required to specify the category you want to share this cookbook to.") + exit(1) + else + data['category'] + end + rescue => e + ui.fatal("Unable to reach Opscode Cookbook Site: #{e.message}. Increase log verbosity (-VV) for more information.") + Chef::Log.debug("\n#{e.backtrace.join("\n")}") + exit(1) + end + end + def do_upload(cookbook_filename, cookbook_category, user_id, user_secret_filename) uri = "https://supermarket.getchef.com/api/v1/cookbooks" diff --git a/lib/chef/knife/core/status_presenter.rb b/lib/chef/knife/core/status_presenter.rb new file mode 100644 index 0000000000..3298d5e4ac --- /dev/null +++ b/lib/chef/knife/core/status_presenter.rb @@ -0,0 +1,156 @@ +# +# Author:: Nicolas DUPEUX (<nicolas.dupeux@arkea.com>) +# Copyright:: Copyright (c) 2011 Opscode, 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/knife/core/text_formatter' +require 'chef/knife/core/generic_presenter' + +class Chef + class Knife + module Core + + # This module may be included into a knife subcommand class to automatically + # add configuration options used by the StatusPresenter + module StatusFormattingOptions + # :nodoc: + # Would prefer to do this in a rational way, but can't be done b/c of + # Mixlib::CLI's design :( + def self.included(includer) + includer.class_eval do + option :medium_output, + :short => '-m', + :long => '--medium', + :boolean => true, + :default => false, + :description => 'Include normal attributes in the output' + + option :long_output, + :short => '-l', + :long => '--long', + :boolean => true, + :default => false, + :description => 'Include all attributes in the output' + end + end + end + + #==Chef::Knife::Core::StatusPresenter + # A customized presenter for Chef::Node objects. Supports variable-length + # output formats for displaying node data + class StatusPresenter < GenericPresenter + + def format(data) + if parse_format_option == :json + summarize_json(data) + else + super + end + end + + def summarize_json(list) + result_list = [] + list.each do |node| + result = {} + + result["name"] = node.name + result["chef_environment"] = node.chef_environment + ip = (node[:ec2] && node[:ec2][:public_ipv4]) || node[:ipaddress] + fqdn = (node[:ec2] && node[:ec2][:public_hostname]) || node[:fqdn] + result["ip"] = ip if ip + result["fqdn"] = fqdn if fqdn + result["run_list"] = node.run_list if config[:run_list] + result["ohai_time"] = node[:ohai_time] + result["platform"] = node[:platform] if node[:platform] + result["platform_version"] = node[:platform_version] if node[:platform_version] + + if config[:long_output] + result["default"] = node.default_attrs + result["override"] = node.override_attrs + result["automatic"] = node.automatic_attrs + end + result_list << result + end + + Chef::JSONCompat.to_json_pretty(result_list) + end + + # Converts a Chef::Node object to a string suitable for output to a + # terminal. If config[:medium_output] or config[:long_output] are set + # the volume of output is adjusted accordingly. Uses colors if enabled + # in the ui object. + def summarize(list) + summarized='' + list.each do |data| + node = data + # special case ec2 with their split horizon whatsis. + ip = (node[:ec2] && node[:ec2][:public_ipv4]) || node[:ipaddress] + fqdn = (node[:ec2] && node[:ec2][:public_hostname]) || node[:fqdn] + + hours, minutes, seconds = time_difference_in_hms(node["ohai_time"]) + hours_text = "#{hours} hour#{hours == 1 ? ' ' : 's'}" + minutes_text = "#{minutes} minute#{minutes == 1 ? ' ' : 's'}" + run_list = "#{node.run_list}" if config[:run_list] + if hours > 24 + color = :red + text = hours_text + elsif hours >= 1 + color = :yellow + text = hours_text + else + color = :green + text = minutes_text + end + + line_parts = Array.new + line_parts << @ui.color(text, color) + ' ago' << node.name + line_parts << fqdn if fqdn + line_parts << ip if ip + line_parts << run_list if run_list + + if node['platform'] + platform = node['platform'] + if node['platform_version'] + platform << " #{node['platform_version']}" + end + line_parts << platform + end + + summarized=summarized + line_parts.join(', ') + ".\n" + end + summarized + end + + def key(key_text) + ui.color(key_text, :cyan) + end + + # :nodoc: + # TODO: this is duplicated from StatusHelper in the Webui. dedup. + def time_difference_in_hms(unix_time) + now = Time.now.to_i + difference = now - unix_time.to_i + hours = (difference / 3600).to_i + difference = difference % 3600 + minutes = (difference / 60).to_i + seconds = (difference % 60) + return [hours, minutes, seconds] + end + + end + end + end +end diff --git a/lib/chef/knife/core/ui.rb b/lib/chef/knife/core/ui.rb index 0007480ea2..f3ecfbcae8 100644 --- a/lib/chef/knife/core/ui.rb +++ b/lib/chef/knife/core/ui.rb @@ -113,7 +113,7 @@ class Chef # determined by the value of `config[:color]`. When output is not to a # terminal, colored output is never used def color? - Chef::Config[:color] && stdout.tty? && !Chef::Platform.windows? + Chef::Config[:color] && stdout.tty? end def ask(*args, &block) diff --git a/lib/chef/knife/status.rb b/lib/chef/knife/status.rb index 5906a4a624..93e81f8f03 100644 --- a/lib/chef/knife/status.rb +++ b/lib/chef/knife/status.rb @@ -17,13 +17,13 @@ # require 'chef/knife' +require 'chef/knife/core/status_presenter' class Chef class Knife class Status < Knife deps do - require 'highline' require 'chef/search/query' end @@ -44,74 +44,27 @@ class Chef :long => "--hide-healthy", :description => "Hide nodes that have run chef in the last hour" - def highline - @h ||= HighLine.new - end - def run + ui.use_presenter Knife::Core::StatusPresenter all_nodes = [] q = Chef::Search::Query.new - query = @name_args[0] || "*:*" + query = @name_args[0] ? @name_args[0].dup : '*:*' + if config[:hide_healthy] + time = Time.now.to_i + query_unhealthy = "NOT ohai_time:[" << (time - 60*60).to_s << " TO " << time.to_s << "]" + query << ' AND ' << query_unhealthy << @name_args[0] if @name_args[0] + query = query_unhealthy unless @name_args[0] + end q.search(:node, query) do |node| all_nodes << node end - all_nodes.sort { |n1, n2| + output(all_nodes.sort { |n1, n2| if (config[:sort_reverse] || Chef::Config[:knife][:sort_status_reverse]) (n2["ohai_time"] or 0) <=> (n1["ohai_time"] or 0) else (n1["ohai_time"] or 0) <=> (n2["ohai_time"] or 0) end - }.each do |node| - if node.has_key?("ec2") - fqdn = node['ec2']['public_hostname'] - ipaddress = node['ec2']['public_ipv4'] - else - fqdn = node['fqdn'] - ipaddress = node['ipaddress'] - end - hours, minutes, seconds = time_difference_in_hms(node["ohai_time"]) - hours_text = "#{hours} hour#{hours == 1 ? ' ' : 's'}" - minutes_text = "#{minutes} minute#{minutes == 1 ? ' ' : 's'}" - run_list = "#{node.run_list}" if config[:run_list] - if hours > 24 - color = :red - text = hours_text - elsif hours >= 1 - color = :yellow - text = hours_text - else - color = :green - text = minutes_text - end - - line_parts = Array.new - line_parts << @ui.color(text, color) + " ago" << node.name - line_parts << fqdn if fqdn - line_parts << ipaddress if ipaddress - line_parts << run_list if run_list - - if node['platform'] - platform = node['platform'] - if node['platform_version'] - platform << " #{node['platform_version']}" - end - line_parts << platform - end - highline.say(line_parts.join(', ') + '.') unless (config[:hide_healthy] && hours < 1) - end - - end - - # :nodoc: - # TODO: this is duplicated from StatusHelper in the Webui. dedup. - def time_difference_in_hms(unix_time) - now = Time.now.to_i - difference = now - unix_time.to_i - hours = (difference / 3600).to_i - difference = difference % 3600 - minutes = (difference / 60).to_i - seconds = (difference % 60) - return [hours, minutes, seconds] + }) end end diff --git a/lib/chef/mixin/command/unix.rb b/lib/chef/mixin/command/unix.rb index b63a02663b..2bad4e6bcf 100644 --- a/lib/chef/mixin/command/unix.rb +++ b/lib/chef/mixin/command/unix.rb @@ -100,9 +100,9 @@ class Chef begin if cmd.kind_of?(Array) - exec(*cmd) + Kernel.exec(*cmd) else - exec(cmd) + Kernel.exec(cmd) end raise 'forty-two' rescue Exception => e diff --git a/lib/chef/platform/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb index a812dcd7ad..9e15ea5658 100644 --- a/lib/chef/platform/provider_mapping.rb +++ b/lib/chef/platform/provider_mapping.rb @@ -197,13 +197,14 @@ class Chef }, :suse => { :default => { - :service => Chef::Provider::Service::Redhat, + :service => Chef::Provider::Service::Systemd, :cron => Chef::Provider::Cron, :package => Chef::Provider::Package::Zypper, :group => Chef::Provider::Group::Gpasswd }, "< 12.0" => { - :group => Chef::Provider::Group::Suse + :group => Chef::Provider::Group::Suse, + :service => Chef::Provider::Service::Redhat } }, :oracle => { diff --git a/lib/chef/provider/cron/unix.rb b/lib/chef/provider/cron/unix.rb index 5cb1bcda41..350f8bda18 100644 --- a/lib/chef/provider/cron/unix.rb +++ b/lib/chef/provider/cron/unix.rb @@ -25,18 +25,21 @@ class Chef class Provider class Cron class Unix < Chef::Provider::Cron + include Chef::Mixin::ShellOut private def read_crontab - crontab = nil - status = popen4("crontab -l #{@new_resource.user}") do |pid, stdin, stdout, stderr| - crontab = stdout.read - end - if status.exitstatus > 1 - raise Chef::Exceptions::Cron, "Error determining state of #{@new_resource.name}, exit: #{status.exitstatus}" + crontab = shell_out('/usr/bin/crontab -l', :user => @new_resource.user) + status = crontab.status.exitstatus + + Chef::Log.debug crontab.format_for_exception if status > 0 + + if status > 1 + raise Chef::Exceptions::Cron, "Error determining state of #{@new_resource.name}, exit: #{status}" end - crontab + return nil if status > 0 + crontab.stdout.chomp << "\n" end def write_crontab(crontab) @@ -47,8 +50,9 @@ class Chef exit_status = 0 error_message = "" begin - status, stdout, stderr = run_command_and_return_stdout_stderr(:command => "/usr/bin/crontab #{tempcron.path}",:user => @new_resource.user) - exit_status = status.exitstatus + crontab_write = shell_out("/usr/bin/crontab #{tempcron.path}", :user => @new_resource.user) + stderr = crontab_write.stderr + exit_status = crontab_write.status.exitstatus # solaris9, 10 on some failures for example invalid 'mins' in crontab fails with exit code of zero :( if stderr && stderr.include?("errors detected in input, no crontab file generated") error_message = stderr diff --git a/lib/chef/provider/dsc_script.rb b/lib/chef/provider/dsc_script.rb index c979800cba..b8ca54f1b8 100644 --- a/lib/chef/provider/dsc_script.rb +++ b/lib/chef/provider/dsc_script.rb @@ -47,9 +47,11 @@ class Chef end def load_current_resource - @dsc_resources_info = run_configuration(:test) - @resource_converged = @dsc_resources_info.all? do |resource| - !resource.changes_state? + if supports_dsc? + @dsc_resources_info = run_configuration(:test) + @resource_converged = @dsc_resources_info.all? do |resource| + !resource.changes_state? + end end end @@ -57,8 +59,26 @@ class Chef true end + def define_resource_requirements + requirements.assert(:run) do |a| + err = [ + 'Could not find Dsc on the system', + powershell_info_str, + "Powershell 4.0 or higher was not detected on your system and is required to use the dsc_script resource.", + ] + a.assertion { supports_dsc? } + a.failure_message Chef::Exceptions::NoProviderAvailable, err.join(' ') + a.whyrun err + ["Assuming a previous resource installs Powershell 4.0 or higher."] + a.block_action! + end + end + protected + def supports_dsc? + run_context && Chef::Platform.supports_dsc?(node) + end + def run_configuration(operation) config_directory = ::Dir.mktmpdir("chef-dsc-script") configuration_data_path = get_configuration_data_path(config_directory) @@ -144,6 +164,14 @@ class Chef end end end + + def powershell_info_str + if run_context && run_context.node[:languages] && run_context.node[:languages][:powershell] + install_info = "Powershell #{run_context.node[:languages][:powershell][:version]} was found on the system." + else + install_info = 'Powershell was not found.' + end + end end end end diff --git a/lib/chef/provider/execute.rb b/lib/chef/provider/execute.rb index d469bea769..54632c0684 100644 --- a/lib/chef/provider/execute.rb +++ b/lib/chef/provider/execute.rb @@ -50,10 +50,11 @@ class Chef opts[:umask] = @new_resource.umask if @new_resource.umask opts[:log_level] = :info opts[:log_tag] = @new_resource.to_s - if STDOUT.tty? && !Chef::Config[:daemon] && Chef::Log.info? + if STDOUT.tty? && !Chef::Config[:daemon] && Chef::Log.info? && !@new_resource.sensitive opts[:live_stream] = STDOUT end - converge_by("execute #{@new_resource.command}") do + description = @new_resource.sensitive ? "sensitive resource" : @new_resource.command + converge_by("execute #{description}") do result = shell_out!(@new_resource.command, opts) Chef::Log.info("#{@new_resource} ran successfully") end diff --git a/lib/chef/provider/git.rb b/lib/chef/provider/git.rb index c8e615c1f9..2ef119e839 100644 --- a/lib/chef/provider/git.rb +++ b/lib/chef/provider/git.rb @@ -64,7 +64,7 @@ class Chef a.failure_message Chef::Exceptions::UnresolvableGitReference, "Unable to parse SHA reference for '#{@new_resource.revision}' in repository '#{@new_resource.repository}'. " + "Verify your (case-sensitive) repository URL and revision.\n" + - "`git ls-remote` output: #{@resolved_reference}" + "`git ls-remote '#{@new_resource.repository}' '#{rev_search_pattern}'` output: #{@resolved_reference}" end end @@ -240,35 +240,55 @@ class Chef # annotated tags, we have to search for "revision*" and # post-process. Special handling for 'HEAD' to ignore a tag # named 'HEAD'. - rev_pattern = case @new_resource.revision - when '', 'HEAD' - 'HEAD' - else - @new_resource.revision + '*' - end - command = git("ls-remote \"#{@new_resource.repository}\" \"#{rev_pattern}\"") - @resolved_reference = shell_out!(command, run_options).stdout - ref_lines = @resolved_reference.split("\n") - refs = ref_lines.map { |line| line.split("\t") } - # first try for ^{} indicating the commit pointed to by an - # annotated tag - tagged_commit = refs.find { |m| m[1].end_with?("#{@new_resource.revision}^{}") } + @resolved_reference = git_ls_remote(rev_search_pattern) + refs = @resolved_reference.split("\n").map { |line| line.split("\t") } + # First try for ^{} indicating the commit pointed to by an + # annotated tag. # It is possible for a user to create a tag named 'HEAD'. # Using such a degenerate annotated tag would be very # confusing. We avoid the issue by disallowing the use of # annotated tags named 'HEAD'. - if tagged_commit && rev_pattern != 'HEAD' - tagged_commit[0] + if rev_search_pattern != 'HEAD' + found = find_revision(refs, @new_resource.revision, '^{}') else - found = refs.find { |m| m[1].end_with?(@new_resource.revision) } - if found - found[0] - else - nil - end + found = refs_search(refs, 'HEAD') + end + found = find_revision(refs, @new_resource.revision) if found.empty? + found.size == 1 ? found.first[0] : nil + end + + def find_revision(refs, revision, suffix="") + found = refs_search(refs, rev_match_pattern('refs/tags/', revision) + suffix) + found = refs_search(refs, rev_match_pattern('refs/heads/', revision) + suffix) if found.empty? + found = refs_search(refs, revision + suffix) if found.empty? + found + end + + def rev_match_pattern(prefix, revision) + if revision.start_with?(prefix) + revision + else + prefix + revision end end + def rev_search_pattern + if ['', 'HEAD'].include? @new_resource.revision + 'HEAD' + else + @new_resource.revision + '*' + end + end + + def git_ls_remote(rev_pattern) + command = git(%Q(ls-remote "#{@new_resource.repository}" "#{rev_pattern}")) + shell_out!(command, run_options).stdout + end + + def refs_search(refs, pattern) + refs.find_all { |m| m[1] == pattern } + end + private def run_options(run_opts={}) diff --git a/lib/chef/provider/lwrp_base.rb b/lib/chef/provider/lwrp_base.rb index 90ce70ae61..135a3f6b7c 100644 --- a/lib/chef/provider/lwrp_base.rb +++ b/lib/chef/provider/lwrp_base.rb @@ -81,22 +81,24 @@ class Chef include Chef::DSL::DataQuery def self.build_from_file(cookbook_name, filename, run_context) + provider_class = nil provider_name = filename_to_qualified_string(cookbook_name, filename) - # Add log entry if we override an existing light-weight provider. class_name = convert_to_class_name(provider_name) if Chef::Provider.const_defined?(class_name) - Chef::Log.info("#{class_name} light-weight provider already initialized -- overriding!") + Chef::Log.info("#{class_name} light-weight provider is already initialized -- Skipping loading #{filename}!") + Chef::Log.debug("Overriding already defined LWRPs is not supported anymore starting with Chef 12.") + provider_class = Chef::Provider.const_get(class_name) + else + provider_class = Class.new(self) + provider_class.class_from_file(filename) + + class_name = convert_to_class_name(provider_name) + Chef::Provider.const_set(class_name, provider_class) + Chef::Log.debug("Loaded contents of #{filename} into a provider named #{provider_name} defined in Chef::Provider::#{class_name}") end - provider_class = Class.new(self) - provider_class.class_from_file(filename) - - class_name = convert_to_class_name(provider_name) - Chef::Provider.const_set(class_name, provider_class) - Chef::Log.debug("Loaded contents of #{filename} into a provider named #{provider_name} defined in Chef::Provider::#{class_name}") - provider_class end diff --git a/lib/chef/provider/package/freebsd/pkgng.rb b/lib/chef/provider/package/freebsd/pkgng.rb index 0741a4d95f..bfe6dca617 100644 --- a/lib/chef/provider/package/freebsd/pkgng.rb +++ b/lib/chef/provider/package/freebsd/pkgng.rb @@ -45,7 +45,7 @@ class Chef def current_installed_version pkg_info = shell_out!("pkg info \"#{@new_resource.package_name}\"", :env => nil, :returns => [0,70]) - pkg_info.stdout[/^#{Regexp.escape(@new_resource.package_name)}-(.+)/, 1] + pkg_info.stdout[/^Version +: (.+)$/, 1] end def candidate_version diff --git a/lib/chef/provider/package/freebsd/port.rb b/lib/chef/provider/package/freebsd/port.rb index 4b3510f0e9..8b191179f0 100644 --- a/lib/chef/provider/package/freebsd/port.rb +++ b/lib/chef/provider/package/freebsd/port.rb @@ -34,7 +34,7 @@ class Chef end def current_installed_version - pkg_info = if supports_pkgng? + pkg_info = if @new_resource.supports_pkgng? shell_out!("pkg info \"#{@new_resource.package_name}\"", :env => nil, :returns => [0,70]) else shell_out!("pkg_info -E \"#{@new_resource.package_name}*\"", :env => nil, :returns => [0,1]) @@ -53,14 +53,6 @@ class Chef def port_dir super(@new_resource.package_name) end - - private - - def supports_pkgng? - with_pkgng = makefile_variable_value('WITH_PKGNG') - with_pkgng && with_pkgng =~ /yes/i - end - end end end diff --git a/lib/chef/provider/package/pacman.rb b/lib/chef/provider/package/pacman.rb index 1014ebcaa5..a9ff0edf7f 100644 --- a/lib/chef/provider/package/pacman.rb +++ b/lib/chef/provider/package/pacman.rb @@ -62,11 +62,11 @@ class Chef package_repos = repos.map {|r| Regexp.escape(r) }.join('|') - status = popen4("pacman -Ss #{@new_resource.package_name}") do |pid, stdin, stdout, stderr| + status = popen4("pacman -Sl") do |pid, stdin, stdout, stderr| stdout.each do |line| case line - when /^(#{package_repos})\/#{Regexp.escape(@new_resource.package_name)} (.+)$/ - # $2 contains a string like "4.4.0-1 (kde kdenetwork)" or "3.10-4 (base)" + when /^(#{package_repos}) #{Regexp.escape(@new_resource.package_name)} (.+)$/ + # $2 contains a string like "4.4.0-1" or "3.10-4 [installed]" # simply split by space and use first token @candidate_version = $2.split(" ").first end diff --git a/lib/chef/provider/package/paludis.rb b/lib/chef/provider/package/paludis.rb index 7c5245fc97..f363b38e50 100644 --- a/lib/chef/provider/package/paludis.rb +++ b/lib/chef/provider/package/paludis.rb @@ -34,7 +34,7 @@ class Chef installed = false re = Regexp.new('(.*)[[:blank:]](.*)[[:blank:]](.*)$') - shell_out!("cave -L warning print-ids -M none -m \"*/#{@new_resource.package_name.split('/').last}\" -f \"%c/%p %v %r\n\"").stdout.each_line do |line| + shell_out!("cave -L warning print-ids -M none -m \"#{@new_resource.package_name}\" -f \"%c/%p %v %r\n\"").stdout.each_line do |line| res = re.match(line) unless res.nil? case res[3] diff --git a/lib/chef/provider/package/rubygems.rb b/lib/chef/provider/package/rubygems.rb index be0022f4aa..6c7e1c066e 100644 --- a/lib/chef/provider/package/rubygems.rb +++ b/lib/chef/provider/package/rubygems.rb @@ -493,6 +493,7 @@ class Chef def target_version_already_installed? return false unless @current_resource && @current_resource.version return false if @current_resource.version.nil? + return false if @new_resource.version.nil? Gem::Requirement.new(@new_resource.version).satisfied_by?(Gem::Version.new(@current_resource.version)) end diff --git a/lib/chef/resource/cookbook_file.rb b/lib/chef/resource/cookbook_file.rb index de758aef71..2709cf64f4 100644 --- a/lib/chef/resource/cookbook_file.rb +++ b/lib/chef/resource/cookbook_file.rb @@ -40,7 +40,7 @@ class Chef end def source(source_filename=nil) - set_or_return(:source, source_filename, :kind_of => String) + set_or_return(:source, source_filename, :kind_of => [ String, Array ]) end def cookbook(cookbook_name=nil) diff --git a/lib/chef/resource/dsc_script.rb b/lib/chef/resource/dsc_script.rb index 2972ace1aa..76ac6659d6 100644 --- a/lib/chef/resource/dsc_script.rb +++ b/lib/chef/resource/dsc_script.rb @@ -28,12 +28,8 @@ class Chef super @allowed_actions.push(:run) @action = :run - if(run_context && Chef::Platform.supports_dsc?(run_context.node)) - @provider = Chef::Provider::DscScript - else - raise Chef::Exceptions::NoProviderAvailable, - "#{powershell_info_str(run_context)}\nPowershell 4.0 or higher was not detected on your system and is required to use the dsc_script resource." - end + @provider = Chef::Provider::DscScript + @resource_name = :dsc_script end def code(arg=nil) @@ -125,16 +121,6 @@ class Chef :kind_of => [ Integer ] ) end - - private - - def powershell_info_str(run_context) - if run_context && run_context.node[:languages] && run_context.node[:languages][:powershell] - install_info = "Powershell #{run_context.node[:languages][:powershell][:version]} was found on the system." - else - install_info = 'Powershell was not found.' - end - end end end end diff --git a/lib/chef/resource/freebsd_package.rb b/lib/chef/resource/freebsd_package.rb index 70ef62ae8a..40cc63fc55 100644 --- a/lib/chef/resource/freebsd_package.rb +++ b/lib/chef/resource/freebsd_package.rb @@ -52,28 +52,27 @@ class Chef "#{created_as_type}[#{name}]" end + def supports_pkgng? + ships_with_pkgng? || !!shell_out!("make -V WITH_PKGNG", :env => nil).stdout.match(/yes/i) + end + private + def ships_with_pkgng? + # It was not until __FreeBSD_version 1000017 that pkgng became + # the default binary package manager. See '/usr/ports/Mk/bsd.port.mk'. + node.automatic[:os_version].to_i >= 1000017 + end + def assign_provider @provider = if @source.to_s =~ /^ports$/i Chef::Provider::Package::Freebsd::Port - elsif ships_with_pkgng? || supports_pkgng? + elsif supports_pkgng? Chef::Provider::Package::Freebsd::Pkgng else Chef::Provider::Package::Freebsd::Pkg end end - - def ships_with_pkgng? - # It was not until __FreeBSD_version 1000017 that pkgng became - # the default binary package manager. See '/usr/ports/Mk/bsd.port.mk'. - node[:os_version].to_i >= 1000017 - end - - def supports_pkgng? - !!shell_out!("make -V WITH_PKGNG", :env => nil).stdout.match(/yes/i) - end - end end end diff --git a/lib/chef/resource/lwrp_base.rb b/lib/chef/resource/lwrp_base.rb index 5b67941a8b..a4606be842 100644 --- a/lib/chef/resource/lwrp_base.rb +++ b/lib/chef/resource/lwrp_base.rb @@ -35,26 +35,24 @@ class Chef # Evaluates the LWRP resource file and instantiates a new Resource class. def self.build_from_file(cookbook_name, filename, run_context) + resource_class = nil rname = filename_to_qualified_string(cookbook_name, filename) - # Add log entry if we override an existing lightweight resource. class_name = convert_to_class_name(rname) if Resource.strict_const_defined?(class_name) - old_class = Resource.send(:remove_const, class_name) - # CHEF-3432 -- Chef::Resource keeps a list of subclasses; need to - # remove old ones from the list when replacing. - resource_classes.delete(old_class) - Chef::Log.info("#{class_name} lightweight resource already initialized -- overriding!") - end - - resource_class = Class.new(self) + Chef::Log.info("#{class_name} light-weight resource is already initialized -- Skipping loading #{filename}!") + Chef::Log.debug("Overriding already defined LWRPs is not supported anymore starting with Chef 12.") + resource_class = Resource.const_get(class_name) + else + resource_class = Class.new(self) - resource_class.resource_name = rname - resource_class.run_context = run_context - resource_class.class_from_file(filename) + resource_class.resource_name = rname + resource_class.run_context = run_context + resource_class.class_from_file(filename) - Chef::Resource.const_set(class_name, resource_class) - Chef::Log.debug("Loaded contents of #{filename} into a resource named #{rname} defined in Chef::Resource::#{class_name}") + Chef::Resource.const_set(class_name, resource_class) + Chef::Log.debug("Loaded contents of #{filename} into a resource named #{rname} defined in Chef::Resource::#{class_name}") + end resource_class end @@ -112,10 +110,21 @@ class Chef if action_names.empty? defined?(@actions) ? @actions : from_superclass(:actions, []).dup else - @actions = action_names + # BC-compat way for checking if actions have already been defined + if defined?(@actions) + @actions.push(*action_names) + else + @actions = action_names + end end end + # @deprecated + def self.valid_actions(*args) + Chef::Log.warn("`valid_actions' is deprecated, please use actions `instead'!") + actions(*args) + end + # Set the run context on the class. Used to provide access to the node # during class definition. def self.run_context=(run_context) diff --git a/lib/chef/resource/template.rb b/lib/chef/resource/template.rb index 9cba6f1c38..8473f5b677 100644 --- a/lib/chef/resource/template.rb +++ b/lib/chef/resource/template.rb @@ -50,7 +50,7 @@ class Chef set_or_return( :source, file, - :kind_of => [ String ] + :kind_of => [ String, Array ] ) end diff --git a/lib/chef/util/powershell/cmdlet_result.rb b/lib/chef/util/powershell/cmdlet_result.rb index af7b3607cd..246701a7bc 100644 --- a/lib/chef/util/powershell/cmdlet_result.rb +++ b/lib/chef/util/powershell/cmdlet_result.rb @@ -16,7 +16,7 @@ # limitations under the License. # -require 'json' +require 'chef/json_compat' class Chef::Util::Powershell class CmdletResult @@ -33,7 +33,7 @@ class Chef::Util::Powershell def return_value if output_format == :object - JSON.parse(@status.stdout) + Chef::JSONCompat.parse(@status.stdout) else @status.stdout end diff --git a/spec/data/bootstrap/test-hints.erb b/spec/data/bootstrap/test-hints.erb index 29ba710b42..7693fdc7c9 100644 --- a/spec/data/bootstrap/test-hints.erb +++ b/spec/data/bootstrap/test-hints.erb @@ -6,7 +6,7 @@ mkdir -p /etc/chef/ohai/hints <% @chef_config[:knife][:hints].each do |name, hash| -%> ( cat <<'EOP' -<%= hash.to_json %> +<%= Chef::JSONCompat.to_json(hash) %> EOP ) > /etc/chef/ohai/hints/<%= name %>.json <% end -%> diff --git a/spec/data/bootstrap/test.erb b/spec/data/bootstrap/test.erb index 7cdc7dfdd0..3a383b47d0 100644 --- a/spec/data/bootstrap/test.erb +++ b/spec/data/bootstrap/test.erb @@ -1 +1 @@ -<%= first_boot.to_json %>
\ No newline at end of file +<%= Chef::JSONCompat.to_json(first_boot) %> diff --git a/spec/data/cb_version_cookbooks/cookbook2/files/test.txt b/spec/data/cb_version_cookbooks/cookbook2/files/test.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/spec/data/cb_version_cookbooks/cookbook2/files/test.txt diff --git a/spec/data/cb_version_cookbooks/cookbook2/templates/test.erb b/spec/data/cb_version_cookbooks/cookbook2/templates/test.erb new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/spec/data/cb_version_cookbooks/cookbook2/templates/test.erb diff --git a/spec/data/cookbooks/ignorken/files/default/not_me.rb b/spec/data/cookbooks/ignorken/files/default/not_me.rb new file mode 100644 index 0000000000..8063e32a95 --- /dev/null +++ b/spec/data/cookbooks/ignorken/files/default/not_me.rb @@ -0,0 +1,2 @@ +a cat walked on the keyboard one day... +(*&(*&(*&(*&(*^%$%^%#^^&(*)(*{}}}}}}}}+++++===)))))) diff --git a/spec/data/cookbooks/ignorken/templates/ubuntu-12.10/not_me.rb b/spec/data/cookbooks/ignorken/templates/ubuntu-12.10/not_me.rb new file mode 100644 index 0000000000..8063e32a95 --- /dev/null +++ b/spec/data/cookbooks/ignorken/templates/ubuntu-12.10/not_me.rb @@ -0,0 +1,2 @@ +a cat walked on the keyboard one day... +(*&(*&(*&(*&(*^%$%^%#^^&(*)(*{}}}}}}}}+++++===)))))) diff --git a/spec/data/cookbooks/openldap/libraries/openldap.rb b/spec/data/cookbooks/openldap/libraries/openldap.rb new file mode 100644 index 0000000000..6a3f058f95 --- /dev/null +++ b/spec/data/cookbooks/openldap/libraries/openldap.rb @@ -0,0 +1,4 @@ +require_relative './openldap/version.rb' + +class OpenLDAP +end diff --git a/spec/data/cookbooks/openldap/libraries/openldap/version.rb b/spec/data/cookbooks/openldap/libraries/openldap/version.rb new file mode 100644 index 0000000000..4bff12b01c --- /dev/null +++ b/spec/data/cookbooks/openldap/libraries/openldap/version.rb @@ -0,0 +1,3 @@ +class OpenLDAP + VERSION = '8.9.10' +end diff --git a/spec/data/lwrp/providers/buck_passer.rb b/spec/data/lwrp/providers/buck_passer.rb index 1f83e5098b..8d5156af81 100644 --- a/spec/data/lwrp/providers/buck_passer.rb +++ b/spec/data/lwrp/providers/buck_passer.rb @@ -1,3 +1,10 @@ -action :buck_stops_here do - log "This should be overwritten by ../lwrp_override/buck_passer.rb" +action :pass_buck do + lwrp_foo :prepared_thumbs do + action :prepare_thumbs + provider :lwrp_thumb_twiddler + end + lwrp_foo :twiddled_thumbs do + action :twiddle_thumbs + provider :lwrp_thumb_twiddler + end end diff --git a/spec/data/lwrp/resources/foo.rb b/spec/data/lwrp/resources/foo.rb index c881c80530..0ee83f0cd0 100644 --- a/spec/data/lwrp/resources/foo.rb +++ b/spec/data/lwrp/resources/foo.rb @@ -1,3 +1,4 @@ -actions :never_execute +actions :prepare_thumbs, :twiddle_thumbs +default_action :pass_buck -attribute :ever, :kind_of => String +attribute :monkey, :kind_of => String diff --git a/spec/data/lwrp_override/providers/buck_passer.rb b/spec/data/lwrp_override/providers/buck_passer.rb index 75917a58c9..2061b391dc 100644 --- a/spec/data/lwrp_override/providers/buck_passer.rb +++ b/spec/data/lwrp_override/providers/buck_passer.rb @@ -1,10 +1,5 @@ -action :pass_buck do - lwrp_foo :prepared_thumbs do - action :prepare_thumbs - provider :lwrp_thumb_twiddler - end - lwrp_foo :twiddled_thumbs do - action :twiddle_thumbs - provider :lwrp_thumb_twiddler - end -end
\ No newline at end of file +# Starting with Chef 12 reloading an LWRP shouldn't reload the file anymore + +action :buck_stops_here do + log "This should be overwritten by ../lwrp_override/buck_passer.rb" +end diff --git a/spec/data/lwrp_override/resources/foo.rb b/spec/data/lwrp_override/resources/foo.rb index 0ee83f0cd0..14decb9634 100644 --- a/spec/data/lwrp_override/resources/foo.rb +++ b/spec/data/lwrp_override/resources/foo.rb @@ -1,4 +1,5 @@ -actions :prepare_thumbs, :twiddle_thumbs -default_action :pass_buck +# Starting with Chef 12 reloading an LWRP shouldn't reload the file anymore -attribute :monkey, :kind_of => String +actions :never_execute + +attribute :ever, :kind_of => String diff --git a/spec/functional/knife/cookbook_delete_spec.rb b/spec/functional/knife/cookbook_delete_spec.rb index ee620bf165..4773fd2185 100644 --- a/spec/functional/knife/cookbook_delete_spec.rb +++ b/spec/functional/knife/cookbook_delete_spec.rb @@ -47,7 +47,7 @@ describe Chef::Knife::CookbookDelete do Chef::Log.level = :debug @knife.name_args = %w{no-such-cookbook} - @api.get("/cookbooks/no-such-cookbook", 404, {'error'=>'dear Tim, no. -Sent from my iPad'}.to_json) + @api.get("/cookbooks/no-such-cookbook", 404, Chef::JSONCompat.to_json({'error'=>'dear Tim, no. -Sent from my iPad'})) end it "logs an error and exits" do @@ -62,7 +62,7 @@ describe Chef::Knife::CookbookDelete do before do @knife.name_args = %w{obsolete-cookbook} @cookbook_list = {'obsolete-cookbook' => { 'versions' => ['version' => '1.0.0']} } - @api.get("/cookbooks/obsolete-cookbook", 200, @cookbook_list.to_json) + @api.get("/cookbooks/obsolete-cookbook", 200, Chef::JSONCompat.to_json(@cookbook_list)) end it "asks for confirmation, then deletes the cookbook" do @@ -105,7 +105,7 @@ describe Chef::Knife::CookbookDelete do versions = ['1.0.0', '1.1.0', '1.2.0'] with_version = lambda { |version| { 'version' => version } } @cookbook_list = {'obsolete-cookbook' => { 'versions' => versions.map(&with_version) } } - @api.get("/cookbooks/obsolete-cookbook", 200, @cookbook_list.to_json) + @api.get("/cookbooks/obsolete-cookbook", 200, Chef::JSONCompat.to_json(@cookbook_list)) end it "deletes all versions of a cookbook when given the '-a' flag" do diff --git a/spec/functional/knife/exec_spec.rb b/spec/functional/knife/exec_spec.rb index 455160fd5c..7eb52d01df 100644 --- a/spec/functional/knife/exec_spec.rb +++ b/spec/functional/knife/exec_spec.rb @@ -47,7 +47,7 @@ describe Chef::Knife::Exec do @node = Chef::Node.new @node.name("ohai-world") response = {"rows" => [@node],"start" => 0,"total" => 1} - @api.get(%r{^/search/node}, 200, response.to_json) + @api.get(%r{^/search/node}, 200, Chef::JSONCompat.to_json(response)) code = "$output.puts nodes.all" @knife.config[:exec] = code @knife.run diff --git a/spec/functional/util/powershell/cmdlet_spec.rb b/spec/functional/util/powershell/cmdlet_spec.rb index 63d1ac09b5..b240a5ec12 100644 --- a/spec/functional/util/powershell/cmdlet_spec.rb +++ b/spec/functional/util/powershell/cmdlet_spec.rb @@ -16,7 +16,7 @@ # limitations under the License. # -require 'json' +require 'chef/json_compat' require File.expand_path('../../../../spec_helper', __FILE__) describe Chef::Util::Powershell::Cmdlet, :windows_only do @@ -91,7 +91,7 @@ describe Chef::Util::Powershell::Cmdlet, :windows_only do it "returns json format data", :windows_powershell_dsc_only do result = cmdlet_alias_requires_switch_or_argument.run({},{},'ls') expect(result.succeeded?).to eq(true) - expect(lambda{JSON.parse(result.return_value)}).not_to raise_error + expect(lambda{Chef::JSONCompat.parse(result.return_value)}).not_to raise_error end end diff --git a/spec/integration/knife/chef_fs_data_store_spec.rb b/spec/integration/knife/chef_fs_data_store_spec.rb index c6737e08cb..a4d62673de 100644 --- a/spec/integration/knife/chef_fs_data_store_spec.rb +++ b/spec/integration/knife/chef_fs_data_store_spec.rb @@ -22,7 +22,7 @@ require 'chef/knife/show' require 'chef/knife/raw' require 'chef/knife/cookbook_upload' -describe 'ChefFSDataStore tests' do +describe 'ChefFSDataStore tests', :workstation do include IntegrationSupport include KnifeSupport diff --git a/spec/integration/knife/chef_repo_path_spec.rb b/spec/integration/knife/chef_repo_path_spec.rb index 90455ed374..874b33901f 100644 --- a/spec/integration/knife/chef_repo_path_spec.rb +++ b/spec/integration/knife/chef_repo_path_spec.rb @@ -20,7 +20,7 @@ require 'support/shared/context/config' require 'chef/knife/list' require 'chef/knife/show' -describe 'chef_repo_path tests' do +describe 'chef_repo_path tests', :workstation do include IntegrationSupport include KnifeSupport diff --git a/spec/integration/knife/chef_repository_file_system_spec.rb b/spec/integration/knife/chef_repository_file_system_spec.rb index ff86924e22..34afd228f3 100644 --- a/spec/integration/knife/chef_repository_file_system_spec.rb +++ b/spec/integration/knife/chef_repository_file_system_spec.rb @@ -19,7 +19,7 @@ require 'support/shared/integration/integration_helper' require 'chef/knife/list' require 'chef/knife/show' -describe 'General chef_repo file system checks' do +describe 'General chef_repo file system checks', :workstation do include IntegrationSupport include KnifeSupport diff --git a/spec/integration/knife/chefignore_spec.rb b/spec/integration/knife/chefignore_spec.rb index b09959b581..34bf391f88 100644 --- a/spec/integration/knife/chefignore_spec.rb +++ b/spec/integration/knife/chefignore_spec.rb @@ -19,7 +19,7 @@ require 'support/shared/integration/integration_helper' require 'chef/knife/list' require 'chef/knife/show' -describe 'chefignore tests' do +describe 'chefignore tests', :workstation do include IntegrationSupport include KnifeSupport diff --git a/spec/integration/knife/common_options_spec.rb b/spec/integration/knife/common_options_spec.rb index 7a48f14ad3..dfc1e024f9 100644 --- a/spec/integration/knife/common_options_spec.rb +++ b/spec/integration/knife/common_options_spec.rb @@ -18,7 +18,7 @@ require 'support/shared/integration/integration_helper' require 'chef/knife/raw' -describe 'knife common options' do +describe 'knife common options', :workstation do include IntegrationSupport include KnifeSupport diff --git a/spec/integration/knife/cookbook_api_ipv6_spec.rb b/spec/integration/knife/cookbook_api_ipv6_spec.rb index c5b5b81abe..e59c8912bd 100644 --- a/spec/integration/knife/cookbook_api_ipv6_spec.rb +++ b/spec/integration/knife/cookbook_api_ipv6_spec.rb @@ -18,7 +18,7 @@ require 'support/shared/integration/integration_helper' require 'chef/mixin/shell_out' -describe "Knife cookbook API integration with IPv6" do +describe "Knife cookbook API integration with IPv6", :workstation do include IntegrationSupport include Chef::Mixin::ShellOut diff --git a/spec/integration/knife/delete_spec.rb b/spec/integration/knife/delete_spec.rb index e56469e102..733a7ef72b 100644 --- a/spec/integration/knife/delete_spec.rb +++ b/spec/integration/knife/delete_spec.rb @@ -20,7 +20,7 @@ require 'chef/knife/delete' require 'chef/knife/list' require 'chef/knife/raw' -describe 'knife delete' do +describe 'knife delete', :workstation do include IntegrationSupport include KnifeSupport diff --git a/spec/integration/knife/deps_spec.rb b/spec/integration/knife/deps_spec.rb index 2cbb7b8f74..8b4d71906b 100644 --- a/spec/integration/knife/deps_spec.rb +++ b/spec/integration/knife/deps_spec.rb @@ -19,7 +19,7 @@ require 'support/shared/integration/integration_helper' require 'support/shared/context/config' require 'chef/knife/deps' -describe 'knife deps' do +describe 'knife deps', :workstation do include IntegrationSupport include KnifeSupport diff --git a/spec/integration/knife/diff_spec.rb b/spec/integration/knife/diff_spec.rb index 62b491d6ef..c12ebbcf8f 100644 --- a/spec/integration/knife/diff_spec.rb +++ b/spec/integration/knife/diff_spec.rb @@ -18,7 +18,7 @@ require 'support/shared/integration/integration_helper' require 'chef/knife/diff' -describe 'knife diff' do +describe 'knife diff', :workstation do include IntegrationSupport include KnifeSupport diff --git a/spec/integration/knife/download_spec.rb b/spec/integration/knife/download_spec.rb index 17779d1f47..0c2b907f1e 100644 --- a/spec/integration/knife/download_spec.rb +++ b/spec/integration/knife/download_spec.rb @@ -19,7 +19,7 @@ require 'support/shared/integration/integration_helper' require 'chef/knife/download' require 'chef/knife/diff' -describe 'knife download' do +describe 'knife download', :workstation do include IntegrationSupport include KnifeSupport diff --git a/spec/integration/knife/list_spec.rb b/spec/integration/knife/list_spec.rb index c9632e146d..3d8b83001d 100644 --- a/spec/integration/knife/list_spec.rb +++ b/spec/integration/knife/list_spec.rb @@ -19,7 +19,7 @@ require 'support/shared/integration/integration_helper' require 'support/shared/context/config' require 'chef/knife/list' -describe 'knife list' do +describe 'knife list', :workstation do include IntegrationSupport include KnifeSupport diff --git a/spec/integration/knife/raw_spec.rb b/spec/integration/knife/raw_spec.rb index fad69e4e11..2b49d2ebb2 100644 --- a/spec/integration/knife/raw_spec.rb +++ b/spec/integration/knife/raw_spec.rb @@ -20,7 +20,7 @@ require 'support/shared/context/config' require 'chef/knife/raw' require 'chef/knife/show' -describe 'knife raw' do +describe 'knife raw', :workstation do include IntegrationSupport include KnifeSupport include AppServerSupport diff --git a/spec/integration/knife/redirection_spec.rb b/spec/integration/knife/redirection_spec.rb index 549a6f6df0..77bda99453 100644 --- a/spec/integration/knife/redirection_spec.rb +++ b/spec/integration/knife/redirection_spec.rb @@ -19,7 +19,7 @@ require 'support/shared/integration/integration_helper' require 'support/shared/context/config' require 'chef/knife/list' -describe 'redirection' do +describe 'redirection', :workstation do include IntegrationSupport include KnifeSupport include AppServerSupport diff --git a/spec/integration/knife/serve_spec.rb b/spec/integration/knife/serve_spec.rb index 32e633543d..3c859b794e 100644 --- a/spec/integration/knife/serve_spec.rb +++ b/spec/integration/knife/serve_spec.rb @@ -19,7 +19,7 @@ require 'support/shared/integration/integration_helper' require 'chef/knife/serve' require 'chef/server_api' -describe 'knife serve' do +describe 'knife serve', :workstation do include IntegrationSupport include KnifeSupport include AppServerSupport diff --git a/spec/integration/knife/show_spec.rb b/spec/integration/knife/show_spec.rb index 4a71499e87..bc7f1cf6d3 100644 --- a/spec/integration/knife/show_spec.rb +++ b/spec/integration/knife/show_spec.rb @@ -19,7 +19,7 @@ require 'support/shared/integration/integration_helper' require 'support/shared/context/config' require 'chef/knife/show' -describe 'knife show' do +describe 'knife show', :workstation do include IntegrationSupport include KnifeSupport diff --git a/spec/integration/knife/upload_spec.rb b/spec/integration/knife/upload_spec.rb index 05d33d1a17..dade476889 100644 --- a/spec/integration/knife/upload_spec.rb +++ b/spec/integration/knife/upload_spec.rb @@ -19,8 +19,9 @@ require 'support/shared/integration/integration_helper' require 'chef/knife/upload' require 'chef/knife/diff' require 'chef/knife/raw' +require 'chef/json_compat' -describe 'knife upload' do +describe 'knife upload', :workstation do include IntegrationSupport include KnifeSupport @@ -261,7 +262,7 @@ Created /data_bags/x/y.json EOM knife('diff --name-status /data_bags').should_succeed <<EOM EOM - JSON.parse(knife('raw /data/x/y').stdout, :create_additions => false).keys.sort.should == [ 'foo', 'id' ] + Chef::JSONCompat.parse(knife('raw /data/x/y').stdout, :create_additions => false).keys.sort.should == [ 'foo', 'id' ] end it 'knife upload /data_bags/x /data_bags/x/y.json uploads x once' do @@ -284,7 +285,7 @@ Created /data_bags/x Created /data_bags/x/y.json EOM knife('diff --name-status /data_bags').should_succeed '' - result = JSON.parse(knife('raw /data/x/y').stdout, :create_additions => false) + result = Chef::JSONCompat.parse(knife('raw /data/x/y').stdout, :create_additions => false) result.keys.sort.should == [ 'chef_type', 'data_bag', 'id' ] result['chef_type'].should == 'aaa' result['data_bag'].should == 'bbb' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ed0a8f89f6..1760aab871 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -97,6 +97,9 @@ RSpec.configure do |config| config.filter_run :focus => true config.filter_run_excluding :external => true + # Only run these tests on platforms that are also chef workstations + config.filter_run_excluding :workstation if solaris? + # Tests that randomly fail, but may have value. config.filter_run_excluding :volatile => true config.filter_run_excluding :volatile_on_solaris => true if solaris? @@ -185,3 +188,6 @@ module WEBrick end end end + +# Enough stuff needs json serialization that I'm just adding it here for equality asserts +require 'chef/json_compat' diff --git a/spec/support/shared/integration/integration_helper.rb b/spec/support/shared/integration/integration_helper.rb index b42f7f69d9..e6942c62af 100644 --- a/spec/support/shared/integration/integration_helper.rb +++ b/spec/support/shared/integration/integration_helper.rb @@ -72,7 +72,7 @@ module IntegrationSupport File.open(filename, 'w') do |file| raw = case contents when Hash, Array - JSON.pretty_generate(contents) + Chef::JSONCompat.to_json_pretty(contents) else contents end diff --git a/spec/support/shared/shared_examples.rb b/spec/support/shared/shared_examples.rb new file mode 100644 index 0000000000..b20c65f8b6 --- /dev/null +++ b/spec/support/shared/shared_examples.rb @@ -0,0 +1,14 @@ +# For storing any examples shared between multiple tests + +# Any object which defines a .to_json should import this test +shared_examples "to_json equalivent to Chef::JSONCompat.to_json" do + + let(:jsonable) { + raise "You must define the subject when including this test" + } + + it "should allow consumers to call #to_json or Chef::JSONCompat.to_json" do + expect(jsonable.to_json).to eq(Chef::JSONCompat.to_json(jsonable)) + end + +end diff --git a/spec/tiny_server.rb b/spec/tiny_server.rb index 7e6ef3a809..a2cfe168d5 100644 --- a/spec/tiny_server.rb +++ b/spec/tiny_server.rb @@ -22,7 +22,6 @@ require 'webrick/https' require 'rack' #require 'thin' require 'singleton' -require 'chef/json_compat' require 'open-uri' require 'chef/config' @@ -152,7 +151,7 @@ module TinyServer :available_routes => @routes, :request => env} # Uncomment me for glorious debugging # pp :not_found => debug_info - [404, {'Content-Type' => 'application/json'}, [ debug_info.to_json ]] + [404, {'Content-Type' => 'application/json'}, [ Chef::JSONCompat.to_json(debug_info) ]] end end diff --git a/spec/unit/api_client_spec.rb b/spec/unit/api_client_spec.rb index 76fc4afb5c..bd6c5ef7fd 100644 --- a/spec/unit/api_client_spec.rb +++ b/spec/unit/api_client_spec.rb @@ -123,6 +123,10 @@ describe Chef::ApiClient do it "does not include the private key if not present" do @json.should_not include("private_key") end + + include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + let(:jsonable) { @client } + end end describe "when deserializing from JSON" do @@ -135,7 +139,7 @@ describe Chef::ApiClient do "validator" => true, "json_class" => "Chef::ApiClient" } - @client = Chef::JSONCompat.from_json(client.to_json) + @client = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(client)) end it "should deserialize to a Chef::ApiClient object" do diff --git a/spec/unit/config_fetcher_spec.rb b/spec/unit/config_fetcher_spec.rb index f6d5436a11..31787a0909 100644 --- a/spec/unit/config_fetcher_spec.rb +++ b/spec/unit/config_fetcher_spec.rb @@ -1,7 +1,8 @@ require 'spec_helper' require 'chef/config_fetcher' + describe Chef::ConfigFetcher do - let(:valid_json) { {:a=>"b"}.to_json } + let(:valid_json) { Chef::JSONCompat.to_json({:a=>"b"}) } let(:invalid_json) { %q[{"syntax-error": "missing quote}] } let(:http) { double("Chef::HTTP::Simple") } diff --git a/spec/unit/cookbook/cookbook_version_loader_spec.rb b/spec/unit/cookbook/cookbook_version_loader_spec.rb index 5772c5352d..4ba4e1de57 100644 --- a/spec/unit/cookbook/cookbook_version_loader_spec.rb +++ b/spec/unit/cookbook/cookbook_version_loader_spec.rb @@ -57,6 +57,11 @@ describe Chef::Cookbook::CookbookVersionLoader do expect(loaded_cookbook.recipe_filenames).to include(full_path("/recipes/return.rb")) end + it "loads libraries" do + expect(loaded_cookbook.library_filenames).to include(full_path('/libraries/openldap.rb')) + expect(loaded_cookbook.library_filenames).to include(full_path('/libraries/openldap/version.rb')) + end + it "loads static files in the files/ dir" do expect(loaded_cookbook.file_filenames).to include(full_path("/files/default/remotedir/remotesubdir/remote_subdir_file1.txt")) expect(loaded_cookbook.file_filenames).to include(full_path("/files/default/remotedir/remotesubdir/remote_subdir_file2.txt")) diff --git a/spec/unit/cookbook/metadata_spec.rb b/spec/unit/cookbook/metadata_spec.rb index e61c85b42b..51814320d4 100644 --- a/spec/unit/cookbook/metadata_spec.rb +++ b/spec/unit/cookbook/metadata_spec.rb @@ -29,7 +29,8 @@ describe Chef::Cookbook::Metadata do @fields = [ :name, :description, :long_description, :maintainer, :maintainer_email, :license, :platforms, :dependencies, :recommendations, :suggestions, :conflicting, :providing, - :replacing, :attributes, :groupings, :recipes, :version] + :replacing, :attributes, :groupings, :recipes, :version, + :source_url, :issues_url ] end it "does not depend on object identity for equality" do @@ -140,6 +141,13 @@ describe Chef::Cookbook::Metadata do metadata.recipes.should eq(Mash.new) end + it "has an empty source_url string" do + metadata.source_url.should eq('') + end + + it "has an empty issues_url string" do + metadata.issues_url.should eq('') + end end describe "validation" do @@ -188,7 +196,9 @@ describe Chef::Cookbook::Metadata do :license => "Apache v2.0", :description => "Foobar!", :long_description => "Much Longer\nSeriously", - :version => "0.6.0" + :version => "0.6.0", + :source_url => "http://example.com", + :issues_url => "http://example.com/issues" } params.sort { |a,b| a.to_s <=> b.to_s }.each do |field, field_value| describe field do @@ -333,7 +343,9 @@ describe Chef::Cookbook::Metadata do "type" => 'string', "required" => 'recommended', "recipes" => [ "mysql::server", "mysql::master" ], - "default" => [ ] + "default" => [ ], + "source_url" => "http://example.com", + "issues_url" => "http://example.com/issues" } metadata.attribute("/db/mysql/databases", attrs).should == attrs end @@ -356,6 +368,24 @@ describe Chef::Cookbook::Metadata do }.should raise_error(ArgumentError) end + it "should not accept anything but a string for the source_url" do + lambda { + metadata.attribute("db/mysql/databases", :source_url => "foo") + }.should_not raise_error + lambda { + metadata.attribute("db/mysql/databases", :source_url => Hash.new) + }.should raise_error(ArgumentError) + end + + it "should not accept anything but a string for the issues_url" do + lambda { + metadata.attribute("db/mysql/databases", :issues_url => "foo") + }.should_not raise_error + lambda { + metadata.attribute("db/mysql/databases", :issues_url => Hash.new) + }.should raise_error(ArgumentError) + end + it "should not accept anything but an array of strings for choice" do lambda { metadata.attribute("db/mysql/databases", :choice => ['dedicated', 'shared']) @@ -623,9 +653,13 @@ describe Chef::Cookbook::Metadata do metadata.version "1.2.3" end + it "should produce the same output from to_json and Chef::JSONCompat" do + expect(metadata.to_json).to eq(Chef::JSONCompat.to_json(metadata)) + end + describe "serialize" do - let(:deserialized_metadata) { Chef::JSONCompat.from_json(metadata.to_json) } + let(:deserialized_metadata) { Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(metadata)) } it "should serialize to a json hash" do deserialized_metadata.should be_a_kind_of(Hash) @@ -648,6 +682,8 @@ describe Chef::Cookbook::Metadata do attributes recipes version + source_url + issues_url }.each do |t| it "should include '#{t}'" do deserialized_metadata[t].should == metadata.send(t.to_sym) @@ -657,7 +693,7 @@ describe Chef::Cookbook::Metadata do describe "deserialize" do - let(:deserialized_metadata) { Chef::Cookbook::Metadata.from_json(metadata.to_json) } + let(:deserialized_metadata) { Chef::Cookbook::Metadata.from_json(Chef::JSONCompat.to_json(metadata)) } it "should deserialize to a Chef::Cookbook::Metadata object" do @@ -681,6 +717,8 @@ describe Chef::Cookbook::Metadata do attributes recipes version + source_url + issues_url }.each do |t| it "should match '#{t}'" do deserialized_metadata.send(t.to_sym).should == metadata.send(t.to_sym) @@ -731,5 +769,4 @@ describe Chef::Cookbook::Metadata do end end - end diff --git a/spec/unit/cookbook/syntax_check_spec.rb b/spec/unit/cookbook/syntax_check_spec.rb index cd1ce96716..4d22e0e920 100644 --- a/spec/unit/cookbook/syntax_check_spec.rb +++ b/spec/unit/cookbook/syntax_check_spec.rb @@ -28,10 +28,12 @@ describe Chef::Cookbook::SyntaxCheck do let(:syntax_check) { Chef::Cookbook::SyntaxCheck.new(cookbook_path) } let(:open_ldap_cookbook_files) do - %w{ attributes/default.rb + %w{ attributes/default.rb attributes/smokey.rb definitions/client.rb definitions/server.rb + libraries/openldap.rb + libraries/openldap/version.rb metadata.rb recipes/default.rb recipes/gigantor.rb @@ -44,9 +46,10 @@ describe Chef::Cookbook::SyntaxCheck do Chef::Log.level = :warn # suppress "Syntax OK" messages @attr_files = %w{default.rb smokey.rb}.map { |f| File.join(cookbook_path, 'attributes', f) } + @libr_files = %w{openldap.rb openldap/version.rb}.map { |f| File.join(cookbook_path, 'libraries', f) } @defn_files = %w{client.rb server.rb}.map { |f| File.join(cookbook_path, 'definitions', f)} @recipes = %w{default.rb gigantor.rb one.rb return.rb}.map { |f| File.join(cookbook_path, 'recipes', f) } - @ruby_files = @attr_files + @defn_files + @recipes + [File.join(cookbook_path, "metadata.rb")] + @ruby_files = @attr_files + @libr_files + @defn_files + @recipes + [File.join(cookbook_path, "metadata.rb")] basenames = %w{ helpers_via_partial_test.erb helper_test.erb openldap_stuff.conf.erb diff --git a/spec/unit/cookbook_loader_spec.rb b/spec/unit/cookbook_loader_spec.rb index f40bbd5696..deaf393d7a 100644 --- a/spec/unit/cookbook_loader_spec.rb +++ b/spec/unit/cookbook_loader_spec.rb @@ -210,7 +210,7 @@ describe Chef::CookbookLoader do aa.to_hash["metadata"].recipes.keys.should include("openldap") expected_desc = "Main Open LDAP configuration" aa.to_hash["metadata"].recipes["openldap"].should == expected_desc - raw = aa.to_hash["metadata"].recipes.to_json + raw = Chef::JSONCompat.to_json(aa.to_hash["metadata"].recipes) search_str = "\"openldap\":\"" key_idx = raw.index(search_str) key_idx.should be > 0 diff --git a/spec/unit/cookbook_version_spec.rb b/spec/unit/cookbook_version_spec.rb index f20bc3766a..8436e5c480 100644 --- a/spec/unit/cookbook_version_spec.rb +++ b/spec/unit/cookbook_version_spec.rb @@ -115,14 +115,13 @@ describe Chef::CookbookVersion do @cookbook[:provider_filenames] = Dir[File.join(@cookbook_root, 'providers', '**', '*.rb')] @cookbook[:root_filenames] = Array(File.join(@cookbook_root, 'README.rdoc')) @cookbook[:metadata_filenames] = Array(File.join(@cookbook_root, 'metadata.json')) - end describe "and a cookbook with the same name" do before do # Currently the cookbook loader finds all the files then tells CookbookVersion # where they are. - @cookbook_version = Chef::CookbookVersion.new("tatft", @cookbook_root) + @cookbook_version = Chef::CookbookVersion.new('tatft', @cookbook_root) @cookbook_version.attribute_filenames = @cookbook[:attribute_filenames] @cookbook_version.definition_filenames = @cookbook[:definition_filenames] @@ -350,6 +349,84 @@ describe Chef::CookbookVersion do readme["specificity"].should == "default" end end + end + + describe 'with a cookbook directory named cookbook2 that has unscoped files' do + before do + @cookbook = Hash.new { |hash, key| hash[key] = [] } + + @cookbook_root = File.join(CHEF_SPEC_DATA, 'cb_version_cookbooks', 'cookbook2') + + # Dunno if the paths here are representitive of what is set by CookbookLoader... + @cookbook[:attribute_filenames] = Dir[File.join(@cookbook_root, 'attributes', '**', '*.rb')] + @cookbook[:definition_filenames] = Dir[File.join(@cookbook_root, 'definitions', '**', '*.rb')] + @cookbook[:file_filenames] = Dir[File.join(@cookbook_root, 'files', '**', '*.*')] + @cookbook[:recipe_filenames] = Dir[File.join(@cookbook_root, 'recipes', '**', '*.rb')] + @cookbook[:template_filenames] = Dir[File.join(@cookbook_root, 'templates', '**', '*.*')] + @cookbook[:library_filenames] = Dir[File.join(@cookbook_root, 'libraries', '**', '*.rb')] + @cookbook[:resource_filenames] = Dir[File.join(@cookbook_root, 'resources', '**', '*.rb')] + @cookbook[:provider_filenames] = Dir[File.join(@cookbook_root, 'providers', '**', '*.rb')] + @cookbook[:root_filenames] = Array(File.join(@cookbook_root, 'README.rdoc')) + @cookbook[:metadata_filenames] = Array(File.join(@cookbook_root, 'metadata.json')) + + @cookbook_version = Chef::CookbookVersion.new('cookbook2', @cookbook_root) + @cookbook_version.attribute_filenames = @cookbook[:attribute_filenames] + @cookbook_version.definition_filenames = @cookbook[:definition_filenames] + @cookbook_version.recipe_filenames = @cookbook[:recipe_filenames] + @cookbook_version.template_filenames = @cookbook[:template_filenames] + @cookbook_version.file_filenames = @cookbook[:file_filenames] + @cookbook_version.library_filenames = @cookbook[:library_filenames] + @cookbook_version.resource_filenames = @cookbook[:resource_filenames] + @cookbook_version.provider_filenames = @cookbook[:provider_filenames] + @cookbook_version.root_filenames = @cookbook[:root_filenames] + @cookbook_version.metadata_filenames = @cookbook[:metadata_filenames] + + # Used to test file-specificity related file lookups + @node = Chef::Node.new + @node.set[:platform] = "ubuntu" + @node.set[:platform_version] = "13.04" + @node.name("testing") + end + + it "should see a template" do + @cookbook_version.should have_template_for_node(@node, "test.erb") + end + + it "should see a template using an array lookup" do + @cookbook_version.should have_template_for_node(@node, ["test.erb"]) + end + + it "should see a template using an array lookup with non-existant elements" do + @cookbook_version.should have_template_for_node(@node, ["missing.txt", "test.erb"]) + end + + it "should see a file" do + @cookbook_version.should have_cookbook_file_for_node(@node, "test.txt") + end + + it "should see a file using an array lookup" do + @cookbook_version.should have_cookbook_file_for_node(@node, ["test.txt"]) + end + + it "should see a file using an array lookup with non-existant elements" do + @cookbook_version.should have_cookbook_file_for_node(@node, ["missing.txt", "test.txt"]) + end + + it "should not see a non-existant template" do + @cookbook_version.should_not have_template_for_node(@node, "missing.erb") + end + + it "should not see a non-existant template using an array lookup" do + @cookbook_version.should_not have_template_for_node(@node, ["missing.erb"]) + end + + it "should not see a non-existant file" do + @cookbook_version.should_not have_cookbook_file_for_node(@node, "missing.txt") + end + + it "should not see a non-existant file using an array lookup" do + @cookbook_version.should_not have_cookbook_file_for_node(@node, ["missing.txt"]) + end end @@ -422,4 +499,8 @@ describe Chef::CookbookVersion do end + include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + let(:jsonable) { Chef::CookbookVersion.new("tatft", '/tmp/blah') } + end + end diff --git a/spec/unit/data_bag_item_spec.rb b/spec/unit/data_bag_item_spec.rb index ead0dadfa2..5972d8a239 100644 --- a/spec/unit/data_bag_item_spec.rb +++ b/spec/unit/data_bag_item_spec.rb @@ -166,7 +166,7 @@ describe Chef::DataBagItem do before(:each) do @data_bag_item.data_bag('mars_volta') @data_bag_item.raw_data = { "id" => "octahedron", "snooze" => { "finally" => :world_will }} - @deserial = Chef::JSONCompat.from_json(@data_bag_item.to_json) + @deserial = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(@data_bag_item)) end it "should deserialize to a Chef::DataBagItem object" do @@ -184,6 +184,10 @@ describe Chef::DataBagItem do it "should have a matching 'snooze' key" do @deserial["snooze"].should == { "finally" => "world_will" } end + + include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + let(:jsonable) { @data_bag_item } + end end describe "when converting to a string" do diff --git a/spec/unit/data_bag_spec.rb b/spec/unit/data_bag_spec.rb index c905277b7c..ff323902eb 100644 --- a/spec/unit/data_bag_spec.rb +++ b/spec/unit/data_bag_spec.rb @@ -59,7 +59,7 @@ describe Chef::DataBag do describe "deserialize" do before(:each) do @data_bag.name('mars_volta') - @deserial = Chef::JSONCompat.from_json(@data_bag.to_json) + @deserial = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(@data_bag)) end it "should deserialize to a Chef::DataBag object" do @@ -72,6 +72,10 @@ describe Chef::DataBag do it "should match '#{t}'" do @deserial.send(t.to_sym).should == @data_bag.send(t.to_sym) end + + include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + let(:jsonable) { @data_bag } + end end end diff --git a/spec/unit/encrypted_data_bag_item_spec.rb b/spec/unit/encrypted_data_bag_item_spec.rb index 5ee245b9bc..9335889ef3 100644 --- a/spec/unit/encrypted_data_bag_item_spec.rb +++ b/spec/unit/encrypted_data_bag_item_spec.rb @@ -168,6 +168,17 @@ describe Chef::EncryptedDataBagItem::Decryptor do let(:plaintext_data) { {"foo" => "bar"} } let(:encryption_key) { "passwd" } let(:decryption_key) { encryption_key } + let(:json_wrapped_data) { Chef::JSONCompat.to_json({"json_wrapper" => plaintext_data}) } + + shared_examples "decryption examples" do + it "decrypts the encrypted value" do + decryptor.decrypted_data.should eq(json_wrapped_data) + end + + it "unwraps the encrypted data and returns it" do + decryptor.for_decrypted_item.should eq plaintext_data + end + end context "when decrypting a version 3 (JSON+aes-256-gcm+random iv+auth tag) encrypted value" do @@ -179,13 +190,7 @@ describe Chef::EncryptedDataBagItem::Decryptor do let(:bogus_auth_tag) { "bogus_auth_tag" } - it "decrypts the encrypted value" do - decryptor.decrypted_data.should eq({"json_wrapper" => plaintext_data}.to_json) - end - - it "unwraps the encrypted data and returns it" do - decryptor.for_decrypted_item.should eq plaintext_data - end + include_examples "decryption examples" it "rejects the data if the authentication tag is wrong" do encrypted_value["auth_tag"] = bogus_auth_tag @@ -240,13 +245,7 @@ describe Chef::EncryptedDataBagItem::Decryptor do Base64.encode64(raw_hmac) end - it "decrypts the encrypted value" do - decryptor.decrypted_data.should eq({"json_wrapper" => plaintext_data}.to_json) - end - - it "unwraps the encrypted data and returns it" do - decryptor.for_decrypted_item.should eq plaintext_data - end + include_examples "decryption examples" it "rejects the data if the hmac is wrong" do encrypted_value["hmac"] = bogus_hmac @@ -270,13 +269,7 @@ describe Chef::EncryptedDataBagItem::Decryptor do decryptor.should be_a_instance_of Chef::EncryptedDataBagItem::Decryptor::Version1Decryptor end - it "decrypts the encrypted value" do - decryptor.decrypted_data.should eq({"json_wrapper" => plaintext_data}.to_json) - end - - it "unwraps the encrypted data and returns it" do - decryptor.for_decrypted_item.should eq plaintext_data - end + include_examples "decryption examples" describe "and the decryption step returns invalid data" do it "raises a decryption failure error" do diff --git a/spec/unit/environment_spec.rb b/spec/unit/environment_spec.rb index 5a2c400d3c..ffb8fbfeaf 100644 --- a/spec/unit/environment_spec.rb +++ b/spec/unit/environment_spec.rb @@ -196,7 +196,7 @@ describe Chef::Environment do %w{name description cookbook_versions}.each do |t| it "should include '#{t}'" do - @json.should =~ /"#{t}":#{Regexp.escape(@environment.send(t.to_sym).to_json)}/ + @json.should =~ /"#{t}":#{Regexp.escape(Chef::JSONCompat.to_json(@environment.send(t.to_sym)))}/ end end @@ -207,6 +207,10 @@ describe Chef::Environment do it "should include 'chef_type'" do @json.should =~ /"chef_type":"environment"/ end + + include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + let(:jsonable) { @environment } + end end describe "from_json" do @@ -222,7 +226,7 @@ describe Chef::Environment do "json_class" => "Chef::Environment", "chef_type" => "environment" } - @environment = Chef::JSONCompat.from_json(@data.to_json) + @environment = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(@data)) end it "should return a Chef::Environment" do diff --git a/spec/unit/exceptions_spec.rb b/spec/unit/exceptions_spec.rb index 3e7b1ba93f..21b0abb9bf 100644 --- a/spec/unit/exceptions_spec.rb +++ b/spec/unit/exceptions_spec.rb @@ -74,5 +74,11 @@ describe Chef::Exceptions do it "should have an exception class of #{exception} which inherits from #{expected_super_class}" do lambda{ raise exception }.should raise_error(expected_super_class) end + + if exception.methods.include?(:to_json) + include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + let(:jsonable) { exception } + end + end end end diff --git a/spec/unit/json_compat_spec.rb b/spec/unit/json_compat_spec.rb index e355a47faa..65d931df70 100644 --- a/spec/unit/json_compat_spec.rb +++ b/spec/unit/json_compat_spec.rb @@ -58,13 +58,17 @@ describe Chef::JSONCompat do describe "when pretty printing an object that defines #to_json" do class Foo def to_json(*a) - {'foo' => 1234}.to_json(*a) + Chef::JSONCompat.to_json({'foo' => 1234, 'bar' => {'baz' => 5678}}, *a) end end it "should work" do f = Foo.new - expect(Chef::JSONCompat.to_json_pretty(f)).to eql("{\n \"foo\": 1234\n}\n") + expect(Chef::JSONCompat.to_json_pretty(f)).to eql("{\n \"foo\": 1234,\n \"bar\": {\n \"baz\": 5678\n }\n}\n") + end + + include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + let(:jsonable) { Foo.new } end end @@ -97,4 +101,10 @@ describe Chef::JSONCompat do end end end + + it "should define .to_json on all classes" do + class SomeClass; end + + expect(SomeClass.new.respond_to?(:to_json)).to eq(true) + end end diff --git a/spec/unit/knife/cookbook_site_share_spec.rb b/spec/unit/knife/cookbook_site_share_spec.rb index 902e5f6115..b85db98d53 100644 --- a/spec/unit/knife/cookbook_site_share_spec.rb +++ b/spec/unit/knife/cookbook_site_share_spec.rb @@ -25,6 +25,8 @@ describe Chef::Knife::CookbookSiteShare do before(:each) do @knife = Chef::Knife::CookbookSiteShare.new + # Merge default settings in. + @knife.merge_configs @knife.name_args = ['cookbook_name', 'AwesomeSausage'] @cookbook = Chef::CookbookVersion.new('cookbook_name') @@ -34,6 +36,9 @@ describe Chef::Knife::CookbookSiteShare do @cookbook_loader.stub(:[]).and_return(@cookbook) Chef::CookbookLoader.stub(:new).and_return(@cookbook_loader) + @noauth_rest = double(Chef::REST) + @knife.stub(:noauth_rest).and_return(@noauth_rest) + @cookbook_uploader = Chef::CookbookUploader.new('herpderp', :rest => "norest") Chef::CookbookUploader.stub(:new).and_return(@cookbook_uploader) @cookbook_uploader.stub(:validate_cookbooks).and_return(true) @@ -48,6 +53,20 @@ describe Chef::Knife::CookbookSiteShare do before(:each) do @knife.stub(:do_upload).and_return(true) + @category_response = { + "name" => "cookbook_name", + "category" => "Testing Category" + } + @bad_category_response = { + "error_code" => "NOT_FOUND", + "error_messages" => [ + "Resource does not exist." + ] + } + end + + it 'should set true to config[:dry_run] as default' do + @knife.config[:dry_run].should be_false end it 'should should print usage and exit when given no arguments' do @@ -57,9 +76,23 @@ describe Chef::Knife::CookbookSiteShare do lambda { @knife.run }.should raise_error(SystemExit) end - it 'should print usage and exit when given only 1 argument' do + it 'should not fail when given only 1 argument and can determine category' do @knife.name_args = ['cookbook_name'] - @knife.should_receive(:show_usage) + @noauth_rest.should_receive(:get_rest).with("http://cookbooks.opscode.com/api/v1/cookbooks/cookbook_name").and_return(@category_response) + @knife.should_receive(:do_upload) + @knife.run + end + + it 'should print error and exit when given only 1 argument and cannot determine category' do + @knife.name_args = ['cookbook_name'] + @noauth_rest.should_receive(:get_rest).with("http://cookbooks.opscode.com/api/v1/cookbooks/cookbook_name").and_return(@bad_category_response) + @knife.ui.should_receive(:fatal) + lambda { @knife.run }.should raise_error(SystemExit) + end + + it 'should print error and exit when given only 1 argument and Chef::REST throws an exception' do + @knife.name_args = ['cookbook_name'] + @noauth_rest.should_receive(:get_rest).with("http://cookbooks.opscode.com/api/v1/cookbooks/cookbook_name") { raise Errno::ECONNREFUSED, "Connection refused" } @knife.ui.should_receive(:fatal) lambda { @knife.run }.should raise_error(SystemExit) end @@ -93,6 +126,26 @@ describe Chef::Knife::CookbookSiteShare do FileUtils.should_receive(:rm_rf) @knife.run end + + context "when the --dry-run flag is specified" do + before do + Chef::CookbookSiteStreamingUploader.stub(:create_build_dir).and_return("/var/tmp/dummy") + @knife.config = { :dry_run => true } + @knife.stub_chain(:shell_out!, :stdout).and_return('file') + end + + it "should list files in the tarball" do + expect(@knife).to receive(:shell_out!).with("tar -czf #{@cookbook.name}.tgz #{@cookbook.name}", {:cwd => "/var/tmp/dummy"}) + expect(@knife).to receive(:shell_out!).with("tar -tzf #{@cookbook.name}.tgz", {:cwd => "/var/tmp/dummy"}) + @knife.run + end + + it "does not upload the cookbook" do + allow(@knife).to receive(:shell_out!).and_return(true) + expect(@knife).not_to receive(:do_upload) + @knife.run + end + end end describe 'do_upload' do @@ -109,7 +162,7 @@ describe Chef::Knife::CookbookSiteShare do end it 'should post the cookbook to "https://supermarket.getchef.com"' do - response_text = {:uri => 'https://supermarket.getchef.com/cookbooks/cookbook_name'}.to_json + response_text = Chef::JSONCompat.to_json({:uri => 'https://supermarket.getchef.com/cookbooks/cookbook_name'}) @upload_response.stub(:body).and_return(response_text) @upload_response.stub(:code).and_return(201) Chef::CookbookSiteStreamingUploader.should_receive(:post).with(/supermarket\.getchef\.com/, anything(), anything(), anything()) @@ -117,7 +170,7 @@ describe Chef::Knife::CookbookSiteShare do end it 'should alert the user when a version already exists' do - response_text = {:error_messages => ['Version already exists']}.to_json + response_text = Chef::JSONCompat.to_json({:error_messages => ['Version already exists']}) @upload_response.stub(:body).and_return(response_text) @upload_response.stub(:code).and_return(409) lambda { @knife.run }.should raise_error(SystemExit) @@ -125,7 +178,7 @@ describe Chef::Knife::CookbookSiteShare do end it 'should pass any errors on to the user' do - response_text = {:error_messages => ["You're holding it wrong"]}.to_json + response_text = Chef::JSONCompat.to_json({:error_messages => ["You're holding it wrong"]}) @upload_response.stub(:body).and_return(response_text) @upload_response.stub(:code).and_return(403) lambda { @knife.run }.should raise_error(SystemExit) @@ -133,7 +186,7 @@ describe Chef::Knife::CookbookSiteShare do end it 'should print the body if no errors are exposed on failure' do - response_text = {:system_error => "Your call was dropped", :reason => "There's a map for that"}.to_json + response_text = Chef::JSONCompat.to_json({:system_error => "Your call was dropped", :reason => "There's a map for that"}) @upload_response.stub(:body).and_return(response_text) @upload_response.stub(:code).and_return(500) @knife.ui.should_receive(:error).with(/#{Regexp.escape(response_text)}/)#.ordered diff --git a/spec/unit/knife/core/bootstrap_context_spec.rb b/spec/unit/knife/core/bootstrap_context_spec.rb index 266991a7dd..cd53088419 100644 --- a/spec/unit/knife/core/bootstrap_context_spec.rb +++ b/spec/unit/knife/core/bootstrap_context_spec.rb @@ -95,13 +95,13 @@ EXPECTED describe "when JSON attributes are given" do let(:config) { {:first_boot_attributes => {:baz => :quux}} } it "adds the attributes to first_boot" do - bootstrap_context.first_boot.to_json.should eq({:baz => :quux, :run_list => run_list}.to_json) + Chef::JSONCompat.to_json(bootstrap_context.first_boot).should eq(Chef::JSONCompat.to_json({:baz => :quux, :run_list => run_list})) end end describe "when JSON attributes are NOT given" do it "sets first_boot equal to run_list" do - bootstrap_context.first_boot.to_json.should eq({:run_list => run_list}.to_json) + Chef::JSONCompat.to_json(bootstrap_context.first_boot).should eq(Chef::JSONCompat.to_json({:run_list => run_list})) end end diff --git a/spec/unit/knife/core/ui_spec.rb b/spec/unit/knife/core/ui_spec.rb index 9044bc2f2f..ed1037ebd5 100644 --- a/spec/unit/knife/core/ui_spec.rb +++ b/spec/unit/knife/core/ui_spec.rb @@ -403,6 +403,34 @@ EOM @ui.format_cookbook_list_for_display(@item).should == response end end + + context "when running on Windows" do + before(:each) do + stdout = double('StringIO', :tty? => true) + @ui.stub(:stdout).and_return(stdout) + Chef::Platform.stub(:windows?) { true } + Chef::Config.reset + end + + after(:each) do + Chef::Config.reset + end + + it "should have color set to true if knife config has color explicitly set to true" do + Chef::Config[:color] = true + @ui.config[:color] = true + expect(@ui.color?).to eql(true) + end + + it "should have color set to false if knife config has color explicitly set to false" do + Chef::Config[:color] = false + expect(@ui.color?).to eql(false) + end + + it "should not have color set to false by default" do + expect(@ui.color?).to eql(false) + end + end end describe "confirm" do diff --git a/spec/unit/knife/data_bag_from_file_spec.rb b/spec/unit/knife/data_bag_from_file_spec.rb index dba5cc282b..8de046e7a4 100644 --- a/spec/unit/knife/data_bag_from_file_spec.rb +++ b/spec/unit/knife/data_bag_from_file_spec.rb @@ -29,7 +29,7 @@ describe Chef::Knife::DataBagFromFile do Chef::Platform.stub(:windows?) { false } Chef::Config[:node_name] = "webmonkey.example.com" FileUtils.mkdir_p([db_folder, db_folder2]) - db_file.write(plain_data.to_json) + db_file.write(Chef::JSONCompat.to_json(plain_data)) db_file.flush allow(knife).to receive(:config).and_return(config) allow(Chef::Knife::Core::ObjectLoader).to receive(:new).and_return(loader) diff --git a/spec/unit/knife/status_spec.rb b/spec/unit/knife/status_spec.rb index 6d8d9d5b25..bb43dd25e5 100644 --- a/spec/unit/knife/status_spec.rb +++ b/spec/unit/knife/status_spec.rb @@ -17,7 +17,6 @@ # require 'spec_helper' -require 'highline' describe Chef::Knife::Status do before(:each) do @@ -30,7 +29,7 @@ describe Chef::Knife::Status do Chef::Search::Query.stub(:new).and_return(query) @knife = Chef::Knife::Status.new @stdout = StringIO.new - @knife.stub(:highline).and_return(HighLine.new(StringIO.new, @stdout)) + @knife.ui.stub(:stdout).and_return(@stdout) end describe "run" do diff --git a/spec/unit/knife_spec.rb b/spec/unit/knife_spec.rb index 2db6b40b28..6d4763e087 100644 --- a/spec/unit/knife_spec.rb +++ b/spec/unit/knife_spec.rb @@ -260,8 +260,25 @@ describe Chef::Knife do knife_command.configure_chef knife_command.config[:opt_with_default].should == "from-cli" end - end + context "verbosity is greater than zero" do + let(:fake_config) { "/does/not/exist/knife.rb" } + + before do + @knife.config[:verbosity] = 1 + @knife.config[:config_file] = fake_config + config_loader = double("Chef::WorkstationConfigLoader", :load => true, :no_config_found? => false, :chef_config_dir => "/etc/chef", :config_location => fake_config) + allow(Chef::WorkstationConfigLoader).to receive(:new).and_return(config_loader) + end + + it "prints the path to the configuration file used" do + @stdout, @stderr, @stdin = StringIO.new, StringIO.new, StringIO.new + @knife.ui = Chef::Knife::UI.new(@stdout, @stderr, @stdin, {}) + expect(Chef::Log).to receive(:info).with("Using configuration from #{fake_config}") + @knife.configure_chef + end + end + end end describe "when first created" do diff --git a/spec/unit/lwrp_spec.rb b/spec/unit/lwrp_spec.rb index 960aff3c36..452e1da2a4 100644 --- a/spec/unit/lwrp_spec.rb +++ b/spec/unit/lwrp_spec.rb @@ -42,7 +42,8 @@ describe "LWRP" do end Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file| - Chef::Log.should_receive(:info).with(/overriding/) + Chef::Log.should_receive(:info).with(/Skipping/) + Chef::Log.should_receive(:debug).with(/anymore/) Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) end end @@ -53,16 +54,15 @@ describe "LWRP" do end Dir[File.expand_path( "lwrp/providers/*", CHEF_SPEC_DATA)].each do |file| - Chef::Log.should_receive(:info).with(/overriding/) + Chef::Log.should_receive(:info).with(/Skipping/) + Chef::Log.should_receive(:debug).with(/anymore/) Chef::Provider::LWRPBase.build_from_file("lwrp", file, nil) end end - it "removes the old LRWP resource class from the list of resource subclasses [CHEF-3432]" do - # CHEF-3432 regression test: - # Chef::Resource keeps a list of all subclasses to assist class inflation - # for json parsing (see Chef::JSONCompat). When replacing LWRP resources, - # we need to ensure the old resource class is remove from that list. + it "keeps the old LRWP resource class in the list of resource subclasses" do + # This was originally CHEF-3432 regression test. But with Chef 12 we are + # not replacing the original classes anymore. Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file| Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) end @@ -71,7 +71,7 @@ describe "LWRP" do Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file| Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) end - Chef::Resource.resource_classes.should_not include(first_lwr_foo_class) + Chef::Resource.resource_classes.should include(first_lwr_foo_class) end it "does not attempt to remove classes from higher up namespaces [CHEF-4117]" do @@ -231,6 +231,27 @@ describe "LWRP" do expect(child.default_action).to eq(:dont_eat) end end + + context "when actions are already defined" do + let(:child) do + Class.new(parent) do + actions :eat + actions :sleep + actions :drink + end + end + + def raise_if_deprecated! + if Chef::VERSION.split('.').first.to_i > 12 + raise "This test should be removed and the associated code should be removed!" + end + end + + it "ammends actions when they are already defined" do + raise_if_deprecated! + expect(child.actions).to eq([:eat, :sleep, :drink]) + end + end end end diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb index 21a978a9c9..00879dcb13 100644 --- a/spec/unit/node_spec.rb +++ b/spec/unit/node_spec.rb @@ -762,6 +762,13 @@ describe Chef::Node do end serialized_node.run_list.should == node.run_list end + + include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + let(:jsonable) { + node.from_file(File.expand_path("nodes/test.example.com.rb", CHEF_SPEC_DATA)) + node + } + end end describe "to_s" do diff --git a/spec/unit/platform_spec.rb b/spec/unit/platform_spec.rb index e8c41cdd77..9a65cbe878 100644 --- a/spec/unit/platform_spec.rb +++ b/spec/unit/platform_spec.rb @@ -278,6 +278,18 @@ describe Chef::Platform do pmap[:package].should eql(Chef::Provider::Package::Ips) end + it "should use the Redhat service provider on SLES11" do + 1.upto(3) do |sp| + pmap = Chef::Platform.find("SUSE", "11.#{sp}") + pmap[:service].should eql(Chef::Provider::Service::Redhat) + end + end + + it "should use the Systemd service provider on SLES12" do + pmap = Chef::Platform.find("SUSE", "12.0") + pmap[:service].should eql(Chef::Provider::Service::Systemd) + end + it "should use the SUSE group provider on SLES11" do 1.upto(3) do |sp| pmap = Chef::Platform.find("SUSE", "11.#{sp}") diff --git a/spec/unit/provider/cron/unix_spec.rb b/spec/unit/provider/cron/unix_spec.rb index 60e09baceb..3d7a5675fc 100644 --- a/spec/unit/provider/cron/unix_spec.rb +++ b/spec/unit/provider/cron/unix_spec.rb @@ -21,26 +21,34 @@ require 'spec_helper' describe Chef::Provider::Cron::Unix do - before do - @node = Chef::Node.new - @events = Chef::EventDispatch::Dispatcher.new - @run_context = Chef::RunContext.new(@node, {}, @events) - @new_resource = Chef::Resource::Cron.new("cronhole some stuff") - @new_resource.user "root" - @new_resource.minute "30" - @new_resource.command "/bin/true" - - @provider = Chef::Provider::Cron::Unix.new(@new_resource, @run_context) + + subject(:provider) { Chef::Provider::Cron::Unix.new(new_resource, run_context) } + + let(:username) { "root" } + + let(:node) { Chef::Node.new } + let(:events) { Chef::EventDispatch::Dispatcher.new } + let(:run_context) { Chef::RunContext.new(node, {}, events) } + let(:new_resource) do + Chef::Resource::Cron.new("cronhole some stuff").tap do |resource| + resource.user username + resource.minute "30" + resource.command "/bin/true" + end end - it "should inherit from Chef::Provider:Cron" do - @provider.should be_a(Chef::Provider::Cron) + let(:status) { double('Process::Status', :exitstatus => exitstatus) } + let(:exitstatus) { 0 } + let(:shell_out) { double('Mixlib::ShellOut', :status => status, :stdout => stdout, :stderr => stderr) } + + it "is a Chef::Provider:Cron" do + expect(provider).to be_a(Chef::Provider::Cron) end describe "read_crontab" do - before :each do - @status = double("Status", :exitstatus => 0) - @stdout = StringIO.new(<<-CRONTAB) + let(:stderr) { "" } + let(:stdout) do + String.new(<<-CRONTAB) 0 2 * * * /some/other/command # Chef Name: something else @@ -48,74 +56,84 @@ describe Chef::Provider::Cron::Unix do # Another comment CRONTAB - @provider.stub(:popen4).and_yield(1234, StringIO.new, @stdout, StringIO.new).and_return(@status) + end + + before do + allow(Chef::Log).to receive(:debug) + allow(shell_out).to receive(:format_for_exception).and_return("formatted command output") + allow(provider).to receive(:shell_out).with('/usr/bin/crontab -l', :user => username).and_return(shell_out) end it "should call crontab -l with the user" do - @provider.should_receive(:popen4).with("crontab -l #{@new_resource.user}").and_return(@status) - @provider.send(:read_crontab) + provider.send(:read_crontab) + expect(provider).to have_received(:shell_out).with('/usr/bin/crontab -l', :user => username) end it "should return the contents of the crontab" do - crontab = @provider.send(:read_crontab) - crontab.should == <<-CRONTAB -0 2 * * * /some/other/command + crontab = provider.send(:read_crontab) + expect(crontab).to eq(stdout) + end -# Chef Name: something else -* 5 * * * /bin/true + context "when the user has no crontab" do + let(:exitstatus) { 1 } -# Another comment -CRONTAB - end + it "should return nil if the user has no crontab" do + expect(provider.send(:read_crontab)).to be_nil + end - it "should return nil if the user has no crontab" do - status = double("Status", :exitstatus => 1) - @provider.stub(:popen4).and_return(status) - @provider.send(:read_crontab).should == nil + it "logs the crontab output to debug" do + provider.send(:read_crontab) + expect(Chef::Log).to have_received(:debug).with("formatted command output") + end end - it "should raise an exception if another error occurs" do - status = double("Status", :exitstatus => 2) - @provider.stub(:popen4).and_return(status) - lambda do - @provider.send(:read_crontab) - end.should raise_error(Chef::Exceptions::Cron, "Error determining state of #{@new_resource.name}, exit: 2") + context "when any other error occurs" do + let (:exitstatus) { 2 } + + it "should raise an exception if another error occurs" do + expect { + provider.send(:read_crontab) + }.to raise_error(Chef::Exceptions::Cron, "Error determining state of #{new_resource.name}, exit: 2") + end + + it "logs the crontab output to debug" do + provider.send(:read_crontab) rescue nil + expect(Chef::Log).to have_received(:debug).with("formatted command output") + end end end describe "write_crontab" do - before :each do - @status = double("Status", :exitstatus => 0) - @provider.stub(:run_command_and_return_stdout_stderr).and_return(@status, String.new, String.new) - @tempfile = double("foo", :path => "/tmp/foo", :close => true) - Tempfile.stub(:new).and_return(@tempfile) - @tempfile.should_receive(:flush) - @tempfile.should_receive(:chmod).with(420) - @tempfile.should_receive(:close!) + let(:stdout) { "" } + let(:stderr) { "" } + let(:tempfile) { double("foo", :path => "/tmp/foo", :close => true) } + + before do + expect(Tempfile).to receive(:new).and_return(tempfile) + expect(tempfile).to receive(:flush) + expect(tempfile).to receive(:chmod).with(420) + expect(tempfile).to receive(:close!) + allow(tempfile).to receive(:<<) + allow(provider).to receive(:shell_out).with("/usr/bin/crontab #{tempfile.path}", :user => username).and_return(shell_out) end it "should call crontab for the user" do - @provider.should_receive(:run_command_and_return_stdout_stderr).with(hash_including(:user => @new_resource.user)) - @tempfile.should_receive(:<<).with("Foo") - @provider.send(:write_crontab, "Foo") + provider.send(:write_crontab, "Foo") + expect(provider).to have_received(:shell_out).with("/usr/bin/crontab #{tempfile.path}", :user => username) end it "should call crontab with a file containing the crontab" do - @provider.should_receive(:run_command_and_return_stdout_stderr) do |args| - (args[:command] =~ %r{\A/usr/bin/crontab (/\S+)\z}).should be_true - $1.should == "/tmp/foo" - @status - end - @tempfile.should_receive(:<<).with("Foo\n# wibble\n wah!!") - @provider.send(:write_crontab, "Foo\n# wibble\n wah!!") + provider.send(:write_crontab, "Foo\n# wibble\n wah!!") + expect(tempfile).to have_received(:<<).with("Foo\n# wibble\n wah!!") end - it "should raise an exception if the command returns non-zero" do - @tempfile.should_receive(:<<).with("Foo") - @status.stub(:exitstatus).and_return(1) - lambda do - @provider.send(:write_crontab, "Foo") - end.should raise_error(Chef::Exceptions::Cron, /Error updating state of #{@new_resource.name}, exit: 1/) + context "when writing the crontab fails" do + let(:exitstatus) { 1 } + it "should raise an exception if the command returns non-zero" do + expect { + provider.send(:write_crontab, "Foo") + }.to raise_error(Chef::Exceptions::Cron, /Error updating state of #{new_resource.name}, exit: 1/) + end end end end diff --git a/spec/unit/provider/dsc_script_spec.rb b/spec/unit/provider/dsc_script_spec.rb index 8a7a7b5c6a..d8fbee3ff9 100644 --- a/spec/unit/provider/dsc_script_spec.rb +++ b/spec/unit/provider/dsc_script_spec.rb @@ -22,123 +22,152 @@ require 'chef/util/dsc/resource_info' require 'spec_helper' describe Chef::Provider::DscScript do - let (:node) { - node = Chef::Node.new - node.automatic[:languages][:powershell][:version] = '4.0' - node - } - let (:events) { Chef::EventDispatch::Dispatcher.new } - let (:run_context) { Chef::RunContext.new(node, {}, events) } - let (:resource) { Chef::Resource::DscScript.new("script", run_context) } - let (:provider) do - Chef::Provider::DscScript.new(resource, run_context) - end - - describe '#load_current_resource' do - it "describes the resource as converged if there were 0 DSC resources" do - allow(provider).to receive(:run_configuration).with(:test).and_return([]) - provider.load_current_resource - provider.instance_variable_get('@resource_converged').should be_true + context 'when DSC is available' do + let (:node) { + node = Chef::Node.new + node.automatic[:languages][:powershell][:version] = '4.0' + node + } + let (:events) { Chef::EventDispatch::Dispatcher.new } + let (:run_context) { Chef::RunContext.new(node, {}, events) } + let (:resource) { Chef::Resource::DscScript.new("script", run_context) } + let (:provider) do + Chef::Provider::DscScript.new(resource, run_context) end - it "describes the resource as not converged if there is 1 DSC resources that is converged" do - dsc_resource_info = Chef::Util::DSC::ResourceInfo.new('resource', false, ['nothing will change something']) - allow(provider).to receive(:run_configuration).with(:test).and_return([dsc_resource_info]) - provider.load_current_resource - provider.instance_variable_get('@resource_converged').should be_true + describe '#load_current_resource' do + it "describes the resource as converged if there were 0 DSC resources" do + allow(provider).to receive(:run_configuration).with(:test).and_return([]) + provider.load_current_resource + provider.instance_variable_get('@resource_converged').should be_true + end + + it "describes the resource as not converged if there is 1 DSC resources that is converged" do + dsc_resource_info = Chef::Util::DSC::ResourceInfo.new('resource', false, ['nothing will change something']) + allow(provider).to receive(:run_configuration).with(:test).and_return([dsc_resource_info]) + provider.load_current_resource + provider.instance_variable_get('@resource_converged').should be_true + end + + it "describes the resource as not converged if there is 1 DSC resources that is not converged" do + dsc_resource_info = Chef::Util::DSC::ResourceInfo.new('resource', true, ['will change something']) + allow(provider).to receive(:run_configuration).with(:test).and_return([dsc_resource_info]) + provider.load_current_resource + provider.instance_variable_get('@resource_converged').should be_false + end + + it "describes the resource as not converged if there are any DSC resources that are not converged" do + dsc_resource_info1 = Chef::Util::DSC::ResourceInfo.new('resource', true, ['will change something']) + dsc_resource_info2 = Chef::Util::DSC::ResourceInfo.new('resource', false, ['nothing will change something']) + + allow(provider).to receive(:run_configuration).with(:test).and_return([dsc_resource_info1, dsc_resource_info2]) + provider.load_current_resource + provider.instance_variable_get('@resource_converged').should be_false + end + + it "describes the resource as converged if all DSC resources that are converged" do + dsc_resource_info1 = Chef::Util::DSC::ResourceInfo.new('resource', false, ['nothing will change something']) + dsc_resource_info2 = Chef::Util::DSC::ResourceInfo.new('resource', false, ['nothing will change something']) + + allow(provider).to receive(:run_configuration).with(:test).and_return([dsc_resource_info1, dsc_resource_info2]) + provider.load_current_resource + provider.instance_variable_get('@resource_converged').should be_true + end end - it "describes the resource as not converged if there is 1 DSC resources that is not converged" do - dsc_resource_info = Chef::Util::DSC::ResourceInfo.new('resource', true, ['will change something']) - allow(provider).to receive(:run_configuration).with(:test).and_return([dsc_resource_info]) - provider.load_current_resource - provider.instance_variable_get('@resource_converged').should be_false + describe '#generate_configuration_document' do + # I think integration tests should cover these cases + + it 'uses configuration_document_from_script_path when a dsc script file is given' do + allow(provider).to receive(:load_current_resource) + resource.command("path_to_script") + generator = double('Chef::Util::DSC::ConfigurationGenerator') + generator.should_receive(:configuration_document_from_script_path) + allow(Chef::Util::DSC::ConfigurationGenerator).to receive(:new).and_return(generator) + provider.send(:generate_configuration_document, 'tmp', nil) + end + + it 'uses configuration_document_from_script_code when a the dsc resource is given' do + allow(provider).to receive(:load_current_resource) + resource.code("ImADSCResource{}") + generator = double('Chef::Util::DSC::ConfigurationGenerator') + generator.should_receive(:configuration_document_from_script_code) + allow(Chef::Util::DSC::ConfigurationGenerator).to receive(:new).and_return(generator) + provider.send(:generate_configuration_document, 'tmp', nil) + end + + it 'should noop if neither code or command are provided' do + allow(provider).to receive(:load_current_resource) + generator = double('Chef::Util::DSC::ConfigurationGenerator') + generator.should_receive(:configuration_document_from_script_code).with('', anything(), anything()) + allow(Chef::Util::DSC::ConfigurationGenerator).to receive(:new).and_return(generator) + provider.send(:generate_configuration_document, 'tmp', nil) + end end - it "describes the resource as not converged if there are any DSC resources that are not converged" do - dsc_resource_info1 = Chef::Util::DSC::ResourceInfo.new('resource', true, ['will change something']) - dsc_resource_info2 = Chef::Util::DSC::ResourceInfo.new('resource', false, ['nothing will change something']) - - allow(provider).to receive(:run_configuration).with(:test).and_return([dsc_resource_info1, dsc_resource_info2]) - provider.load_current_resource - provider.instance_variable_get('@resource_converged').should be_false + describe 'action_run' do + it 'should converge the script if it is not converged' do + dsc_resource_info = Chef::Util::DSC::ResourceInfo.new('resource', true, ['will change something']) + allow(provider).to receive(:run_configuration).with(:test).and_return([dsc_resource_info]) + allow(provider).to receive(:run_configuration).with(:set) + + provider.run_action(:run) + resource.should be_updated + end + + it 'should not converge if the script is already converged' do + allow(provider).to receive(:run_configuration).with(:test).and_return([]) + + provider.run_action(:run) + resource.should_not be_updated + end end - it "describes the resource as converged if all DSC resources that are converged" do - dsc_resource_info1 = Chef::Util::DSC::ResourceInfo.new('resource', false, ['nothing will change something']) - dsc_resource_info2 = Chef::Util::DSC::ResourceInfo.new('resource', false, ['nothing will change something']) - - allow(provider).to receive(:run_configuration).with(:test).and_return([dsc_resource_info1, dsc_resource_info2]) - provider.load_current_resource - provider.instance_variable_get('@resource_converged').should be_true + describe '#generate_description' do + it 'removes the resource name from the beginning of any log line from the LCM' do + dsc_resource_info = Chef::Util::DSC::ResourceInfo.new('resourcename', true, ['resourcename doing something', 'lastline']) + provider.instance_variable_set('@dsc_resources_info', [dsc_resource_info]) + provider.send(:generate_description)[1].should match(/converge DSC resource resourcename by doing something/) + end + + it 'ignores the last line' do + dsc_resource_info = Chef::Util::DSC::ResourceInfo.new('resourcename', true, ['resourcename doing something', 'lastline']) + provider.instance_variable_set('@dsc_resources_info', [dsc_resource_info]) + provider.send(:generate_description)[1].should_not match(/lastline/) + end + + it 'reports a dsc resource has not been changed if the LCM reported no change was required' do + dsc_resource_info = Chef::Util::DSC::ResourceInfo.new('resourcename', false, ['resourcename does nothing', 'lastline']) + provider.instance_variable_set('@dsc_resources_info', [dsc_resource_info]) + provider.send(:generate_description)[1].should match(/converge DSC resource resourcename by doing nothing/) + end end end - describe '#generate_configuration_document' do - # I think integration tests should cover these cases - - it 'uses configuration_document_from_script_path when a dsc script file is given' do - allow(provider).to receive(:load_current_resource) - resource.command("path_to_script") - generator = double('Chef::Util::DSC::ConfigurationGenerator') - generator.should_receive(:configuration_document_from_script_path) - allow(Chef::Util::DSC::ConfigurationGenerator).to receive(:new).and_return(generator) - provider.send(:generate_configuration_document, 'tmp', nil) - end - - it 'uses configuration_document_from_script_code when a the dsc resource is given' do - allow(provider).to receive(:load_current_resource) - resource.code("ImADSCResource{}") - generator = double('Chef::Util::DSC::ConfigurationGenerator') - generator.should_receive(:configuration_document_from_script_code) - allow(Chef::Util::DSC::ConfigurationGenerator).to receive(:new).and_return(generator) - provider.send(:generate_configuration_document, 'tmp', nil) - end - - it 'should noop if neither code or command are provided' do - allow(provider).to receive(:load_current_resource) - generator = double('Chef::Util::DSC::ConfigurationGenerator') - generator.should_receive(:configuration_document_from_script_code).with('', anything(), anything()) - allow(Chef::Util::DSC::ConfigurationGenerator).to receive(:new).and_return(generator) - provider.send(:generate_configuration_document, 'tmp', nil) - end - end - - describe 'action_run' do - it 'should converge the script if it is not converged' do - dsc_resource_info = Chef::Util::DSC::ResourceInfo.new('resource', true, ['will change something']) - allow(provider).to receive(:run_configuration).with(:test).and_return([dsc_resource_info]) - allow(provider).to receive(:run_configuration).with(:set) - - provider.run_action(:run) - resource.should be_updated - end - - it 'should not converge if the script is already converged' do - allow(provider).to receive(:run_configuration).with(:test).and_return([]) - - provider.run_action(:run) - resource.should_not be_updated - end - end - - describe '#generate_description' do - it 'removes the resource name from the beginning of any log line from the LCM' do - dsc_resource_info = Chef::Util::DSC::ResourceInfo.new('resourcename', true, ['resourcename doing something', 'lastline']) - provider.instance_variable_set('@dsc_resources_info', [dsc_resource_info]) - provider.send(:generate_description)[1].should match(/converge DSC resource resourcename by doing something/) - end - - it 'ignores the last line' do - dsc_resource_info = Chef::Util::DSC::ResourceInfo.new('resourcename', true, ['resourcename doing something', 'lastline']) - provider.instance_variable_set('@dsc_resources_info', [dsc_resource_info]) - provider.send(:generate_description)[1].should_not match(/lastline/) - end + context 'when Dsc is not available' do + let (:node) { Chef::Node.new } + let (:events) { Chef::EventDispatch::Dispatcher.new } + let (:run_context) { Chef::RunContext.new(node, {}, events) } + let (:resource) { Chef::Resource::DscScript.new('script', run_context) } + let (:provider) { Chef::Provider::DscScript.new(resource, run_context) } + + describe 'action_run' do + ['1.0', '2.0', '3.0'].each do |version| + it "raises an exception for powershell version '#{version}'" do + node.automatic[:languages][:powershell][:version] = version + + expect { + provider.run_action(:run) + }.to raise_error(Chef::Exceptions::NoProviderAvailable) + end + end + + it 'raises an exception if Powershell is not present' do + expect { + provider.run_action(:run) + }.to raise_error(Chef::Exceptions::NoProviderAvailable) + end - it 'reports a dsc resource has not been changed if the LCM reported no change was required' do - dsc_resource_info = Chef::Util::DSC::ResourceInfo.new('resourcename', false, ['resourcename does nothing', 'lastline']) - provider.instance_variable_set('@dsc_resources_info', [dsc_resource_info]) - provider.send(:generate_description)[1].should match(/converge DSC resource resourcename by doing nothing/) end end end diff --git a/spec/unit/provider/execute_spec.rb b/spec/unit/provider/execute_spec.rb index 78216a89fa..6aa48f1e2a 100644 --- a/spec/unit/provider/execute_spec.rb +++ b/spec/unit/provider/execute_spec.rb @@ -36,22 +36,38 @@ describe Chef::Provider::Execute do STDOUT.stub(:tty?).and_return(true) end + let(:opts) do + { + timeout: @new_resource.timeout, + returns: @new_resource.returns, + log_level: :info, + log_tag: @new_resource.to_s, + live_stream: STDOUT + } + end it "should execute foo_resource" do @provider.stub(:load_current_resource) - opts = {} - opts[:timeout] = @new_resource.timeout - opts[:returns] = @new_resource.returns - opts[:log_level] = :info - opts[:log_tag] = @new_resource.to_s - opts[:live_stream] = STDOUT @provider.should_receive(:shell_out!).with(@new_resource.command, opts) + @provider.should_receive(:converge_by).with("execute foo_resource").and_call_original Chef::Log.should_not_receive(:warn) @provider.run_action(:run) @new_resource.should be_updated end + it "should honor sensitive attribute" do + @new_resource.sensitive true + @provider = Chef::Provider::Execute.new(@new_resource, @run_context) + @provider.stub(:load_current_resource) + # Since the resource is sensitive, it should not have :live_stream set + @provider.should_receive(:shell_out!).with(@new_resource.command, opts.reject { |k| k == :live_stream }) + Chef::Log.should_not_receive(:warn) + @provider.should_receive(:converge_by).with("execute sensitive resource").and_call_original + @provider.run_action(:run) + @new_resource.should be_updated + end + it "should do nothing if the sentinel file exists" do @provider.stub(:load_current_resource) File.should_receive(:exists?).with(@new_resource.creates).and_return(true) diff --git a/spec/unit/provider/git_spec.rb b/spec/unit/provider/git_spec.rb index ff1c0b0398..02d155efbd 100644 --- a/spec/unit/provider/git_spec.rb +++ b/spec/unit/provider/git_spec.rb @@ -106,6 +106,52 @@ describe Chef::Provider::Git do @provider.target_revision.should eql("663c22a5e41f5ae3193460cca044ed1435029f53") end + it "converts resource.revision from a tag to a SHA using an exact match" do + @resource.revision "v1.0" + @stdout = ("d03c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n" + + "663c22a5e41f5ae3193460cca044ed1435029f53\trefs/tags/releases/v1.0\n" + + "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/tags/v1.0\n") + @provider.should_receive(:shell_out!).with(@git_ls_remote + "\"v1.0*\"", {:log_tag=>"git[web2.0 app]"}).and_return(double("ShellOut result", :stdout => @stdout)) + @provider.target_revision.should eql("503c22a5e41f5ae3193460cca044ed1435029f53") + end + + it "converts resource.revision from a tag to a SHA, matching tags first, then heads" do + @resource.revision "v1.0" + @stdout = ("d03c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n" + + "663c22a5e41f5ae3193460cca044ed1435029f53\trefs/tags/v1.0\n" + + "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/v1.0\n") + @provider.should_receive(:shell_out!).with(@git_ls_remote + "\"v1.0*\"", {:log_tag=>"git[web2.0 app]"}).and_return(double("ShellOut result", :stdout => @stdout)) + @provider.target_revision.should eql("663c22a5e41f5ae3193460cca044ed1435029f53") + end + + it "converts resource.revision from a tag to a SHA, matching heads if no tags match" do + @resource.revision "v1.0" + @stdout = ("d03c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n" + + "663c22a5e41f5ae3193460cca044ed1435029f53\trefs/tags/v1.1\n" + + "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/v1.0\n") + @provider.should_receive(:shell_out!).with(@git_ls_remote + "\"v1.0*\"", {:log_tag=>"git[web2.0 app]"}).and_return(double("ShellOut result", :stdout => @stdout)) + @provider.target_revision.should eql("503c22a5e41f5ae3193460cca044ed1435029f53") + end + + it "converts resource.revision from a tag to a SHA, matching tags first, then heads, then revision" do + @resource.revision "refs/pulls/v1.0" + @stdout = ("d03c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n" + + "663c22a5e41f5ae3193460cca044ed1435029f53\trefs/tags/v1.0\n" + + "805c22a5e41f5ae3193460cca044ed1435029f53\trefs/pulls/v1.0\n" + + "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/v1.0\n") + @provider.should_receive(:shell_out!).with(@git_ls_remote + "\"refs/pulls/v1.0*\"", {:log_tag=>"git[web2.0 app]"}).and_return(double("ShellOut result", :stdout => @stdout)) + @provider.target_revision.should eql("805c22a5e41f5ae3193460cca044ed1435029f53") + end + + it "converts resource.revision from a tag to a SHA, using full path if provided" do + @resource.revision "refs/heads/v1.0" + @stdout = ("d03c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n" + + "663c22a5e41f5ae3193460cca044ed1435029f53\trefs/tags/v1.0\n" + + "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/v1.0\n") + @provider.should_receive(:shell_out!).with(@git_ls_remote + "\"refs/heads/v1.0*\"", {:log_tag=>"git[web2.0 app]"}).and_return(double("ShellOut result", :stdout => @stdout)) + @provider.target_revision.should eql("503c22a5e41f5ae3193460cca044ed1435029f53") + end + it "raises an invalid remote reference error if you try to deploy from ``origin'' and assertions are run" do @resource.revision "origin/" @provider.action = :checkout diff --git a/spec/unit/provider/package/freebsd/pkgng_spec.rb b/spec/unit/provider/package/freebsd/pkgng_spec.rb index 001c9e23ba..c3837a251b 100644 --- a/spec/unit/provider/package/freebsd/pkgng_spec.rb +++ b/spec/unit/provider/package/freebsd/pkgng_spec.rb @@ -67,7 +67,7 @@ describe Chef::Provider::Package::Freebsd::Port do describe "determining current installed version" do before(:each) do @provider.stub(:supports_pkgng?) - @pkg_info = OpenStruct.new(:stdout => "zsh-3.1.7\n") + @pkg_info = OpenStruct.new(:stdout => "zsh-3.1.7\nVersion : 3.1.7\n") end it "should query pkg database" do diff --git a/spec/unit/provider/package/freebsd/port_spec.rb b/spec/unit/provider/package/freebsd/port_spec.rb index e946719451..8725e5440f 100644 --- a/spec/unit/provider/package/freebsd/port_spec.rb +++ b/spec/unit/provider/package/freebsd/port_spec.rb @@ -26,7 +26,7 @@ describe Chef::Provider::Package::Freebsd::Port do @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) - @new_resource = Chef::Resource::Package.new("zsh") + @new_resource = Chef::Resource::FreebsdPackage.new("zsh", @run_context) @provider = Chef::Provider::Package::Freebsd::Port.new(@new_resource, @run_context) end @@ -66,24 +66,34 @@ describe Chef::Provider::Package::Freebsd::Port do describe "determining current installed version" do before(:each) do - @provider.stub(:supports_pkgng?) @pkg_info = OpenStruct.new(:stdout => "zsh-3.1.7\n") end it "should check 'pkg_info' if system uses pkg_* tools" do - @provider.should_receive(:supports_pkgng?).and_return(false) + @new_resource.stub(:supports_pkgng?) + @new_resource.should_receive(:supports_pkgng?).and_return(false) @provider.should_receive(:shell_out!).with('pkg_info -E "zsh*"', :env => nil, :returns => [0,1]).and_return(@pkg_info) @provider.current_installed_version.should == "3.1.7" end - it "should check 'pkg info' if system uses pkgng" do - @provider.should_receive(:supports_pkgng?).and_return(true) + it "should check 'pkg info' if make supports WITH_PKGNG if freebsd version is < 1000017" do + pkg_enabled = OpenStruct.new(:stdout => "yes\n") + [1000016, 1000000, 901503, 902506, 802511].each do |__freebsd_version| + @node.automatic_attrs[:os_version] = __freebsd_version + @new_resource.should_receive(:shell_out!).with('make -V WITH_PKGNG', :env => nil).and_return(pkg_enabled) + @provider.should_receive(:shell_out!).with('pkg info "zsh"', :env => nil, :returns => [0,70]).and_return(@pkg_info) + @provider.current_installed_version.should == "3.1.7" + end + end + + it "should check 'pkg info' if the freebsd version is greater than or equal to 1000017" do + __freebsd_version = 1000017 + @node.automatic_attrs[:os_version] = __freebsd_version @provider.should_receive(:shell_out!).with('pkg info "zsh"', :env => nil, :returns => [0,70]).and_return(@pkg_info) @provider.current_installed_version.should == "3.1.7" end end - describe "determining candidate version" do before(:each) do @port_version = OpenStruct.new(:stdout => "5.0.5\n", :exitstatus => 0) diff --git a/spec/unit/provider/package/pacman_spec.rb b/spec/unit/provider/package/pacman_spec.rb index 0c1c487980..ed10513350 100644 --- a/spec/unit/provider/package/pacman_spec.rb +++ b/spec/unit/provider/package/pacman_spec.rb @@ -94,10 +94,7 @@ PACMAN end it "should set the candidate version if pacman has one" do - @stdout.stub(:each).and_yield("core/nano 2.2.3-1 (base)"). - and_yield(" Pico editor clone with enhancements"). - and_yield("community/nanoblogger 3.4.1-1"). - and_yield(" NanoBlogger is a small weblog engine written in Bash for the command line") + @stdout.stub(:each).and_yield("core nano 2.2.3-1") @provider.stub(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) @provider.load_current_resource @provider.candidate_version.should eql("2.2.3-1") @@ -124,8 +121,7 @@ PACMAN_CONF ::File.stub(:exists?).with("/etc/pacman.conf").and_return(true) ::File.stub(:read).with("/etc/pacman.conf").and_return(@pacman_conf) - @stdout.stub(:each).and_yield("customrepo/nano 1.2.3-4"). - and_yield(" My custom package") + @stdout.stub(:each).and_yield("customrepo nano 1.2.3-4") @provider.stub(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) @provider.load_current_resource diff --git a/spec/unit/provider/package/paludis_spec.rb b/spec/unit/provider/package/paludis_spec.rb index c99600e535..8387bb1252 100644 --- a/spec/unit/provider/package/paludis_spec.rb +++ b/spec/unit/provider/package/paludis_spec.rb @@ -59,7 +59,7 @@ PKG_STATUS end it "should run pkg info with the package name" do - @provider.should_receive(:shell_out!).with("cave -L warning print-ids -M none -m \"*/#{@new_resource.package_name.split('/').last}\" -f \"%c/%p %v %r\n\"").and_return(@shell_out) + @provider.should_receive(:shell_out!).with("cave -L warning print-ids -M none -m \"#{@new_resource.package_name}\" -f \"%c/%p %v %r\n\"").and_return(@shell_out) @provider.load_current_resource end diff --git a/spec/unit/provider/package/rubygems_spec.rb b/spec/unit/provider/package/rubygems_spec.rb index d3cb9cf7fa..f4d0cebf62 100644 --- a/spec/unit/provider/package/rubygems_spec.rb +++ b/spec/unit/provider/package/rubygems_spec.rb @@ -359,10 +359,12 @@ RBX_GEM_ENV end describe Chef::Provider::Package::Rubygems do + let(:target_version) { nil } + before(:each) do @node = Chef::Node.new @new_resource = Chef::Resource::GemPackage.new("rspec-core") - @spec_version = @new_resource.version RSpec::Core::Version::STRING + @spec_version = @new_resource.version(target_version) @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @@ -371,269 +373,281 @@ describe Chef::Provider::Package::Rubygems do @provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context) end - it "triggers a gem configuration load so a later one will not stomp its config values" do - # ugly, is there a better way? - Gem.instance_variable_get(:@configuration).should_not be_nil - end + describe "when new_resource version is nil" do + let(:target_version) { nil } - it "uses the CurrentGemEnvironment implementation when no gem_binary_path is provided" do - @provider.gem_env.should be_a_kind_of(Chef::Provider::Package::Rubygems::CurrentGemEnvironment) + it "target_version_already_installed? should return false so that we can search for candidates" do + @provider.load_current_resource + @provider.target_version_already_installed?.should be_false + end end - it "uses the AlternateGemEnvironment implementation when a gem_binary_path is provided" do - @new_resource.gem_binary('/usr/weird/bin/gem') - provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context) - provider.gem_env.gem_binary_location.should == '/usr/weird/bin/gem' - end + describe "when new_resource version is current rspec version" do + let(:target_version) { RSpec::Core::Version::STRING } - it "searches for a gem binary when running on Omnibus on Unix" do - platform_mock :unix do - RbConfig::CONFIG.stub(:[]).with('bindir').and_return("/opt/chef/embedded/bin") - ENV.stub(:[]).with('PATH').and_return("/usr/bin:/usr/sbin:/opt/chef/embedded/bin") - File.stub(:exists?).with('/usr/bin/gem').and_return(false) - File.stub(:exists?).with('/usr/sbin/gem').and_return(true) - File.stub(:exists?).with('/opt/chef/embedded/bin/gem').and_return(true) # should not get here - provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context) - provider.gem_env.gem_binary_location.should == '/usr/sbin/gem' + it "triggers a gem configuration load so a later one will not stomp its config values" do + # ugly, is there a better way? + Gem.instance_variable_get(:@configuration).should_not be_nil end - end - it "searches for a gem binary when running on Omnibus on Windows" do - platform_mock :windows do - RbConfig::CONFIG.stub(:[]).with('bindir').and_return("d:/opscode/chef/embedded/bin") - ENV.stub(:[]).with('PATH').and_return('C:\windows\system32;C:\windows;C:\Ruby186\bin;d:\opscode\chef\embedded\bin') - File.stub(:exists?).with('C:\\windows\\system32\\gem').and_return(false) - File.stub(:exists?).with('C:\\windows\\gem').and_return(false) - File.stub(:exists?).with('C:\\Ruby186\\bin\\gem').and_return(true) - File.stub(:exists?).with('d:\\opscode\\chef\\bin\\gem').and_return(false) # should not get here - File.stub(:exists?).with('d:\\opscode\\chef\\embedded\\bin\\gem').and_return(false) # should not get here - provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context) - provider.gem_env.gem_binary_location.should == 'C:\Ruby186\bin\gem' + it "uses the CurrentGemEnvironment implementation when no gem_binary_path is provided" do + @provider.gem_env.should be_a_kind_of(Chef::Provider::Package::Rubygems::CurrentGemEnvironment) end - end - - it "smites you when you try to use a hash of install options with an explicit gem binary" do - @new_resource.gem_binary('/foo/bar') - @new_resource.options(:fail => :burger) - lambda {Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)}.should raise_error(ArgumentError) - end - it "converts the new resource into a gem dependency" do - @provider.gem_dependency.should == Gem::Dependency.new('rspec-core', @spec_version) - @new_resource.version('~> 1.2.0') - @provider.gem_dependency.should == Gem::Dependency.new('rspec-core', '~> 1.2.0') - end + it "uses the AlternateGemEnvironment implementation when a gem_binary_path is provided" do + @new_resource.gem_binary('/usr/weird/bin/gem') + provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context) + provider.gem_env.gem_binary_location.should == '/usr/weird/bin/gem' + end - describe "when determining the currently installed version" do + it "searches for a gem binary when running on Omnibus on Unix" do + platform_mock :unix do + RbConfig::CONFIG.stub(:[]).with('bindir').and_return("/opt/chef/embedded/bin") + ENV.stub(:[]).with('PATH').and_return("/usr/bin:/usr/sbin:/opt/chef/embedded/bin") + File.stub(:exists?).with('/usr/bin/gem').and_return(false) + File.stub(:exists?).with('/usr/sbin/gem').and_return(true) + File.stub(:exists?).with('/opt/chef/embedded/bin/gem').and_return(true) # should not get here + provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context) + provider.gem_env.gem_binary_location.should == '/usr/sbin/gem' + end + end - it "sets the current version to the version specified by the new resource if that version is installed" do - @provider.load_current_resource - @provider.current_resource.version.should == @spec_version + it "searches for a gem binary when running on Omnibus on Windows" do + platform_mock :windows do + RbConfig::CONFIG.stub(:[]).with('bindir').and_return("d:/opscode/chef/embedded/bin") + ENV.stub(:[]).with('PATH').and_return('C:\windows\system32;C:\windows;C:\Ruby186\bin;d:\opscode\chef\embedded\bin') + File.stub(:exists?).with('C:\\windows\\system32\\gem').and_return(false) + File.stub(:exists?).with('C:\\windows\\gem').and_return(false) + File.stub(:exists?).with('C:\\Ruby186\\bin\\gem').and_return(true) + File.stub(:exists?).with('d:\\opscode\\chef\\bin\\gem').and_return(false) # should not get here + File.stub(:exists?).with('d:\\opscode\\chef\\embedded\\bin\\gem').and_return(false) # should not get here + provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context) + provider.gem_env.gem_binary_location.should == 'C:\Ruby186\bin\gem' + end end - it "sets the current version to the highest installed version if the requested version is not installed" do - @new_resource.version('9000.0.2') - @provider.load_current_resource - @provider.current_resource.version.should == @spec_version + it "smites you when you try to use a hash of install options with an explicit gem binary" do + @new_resource.gem_binary('/foo/bar') + @new_resource.options(:fail => :burger) + lambda {Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)}.should raise_error(ArgumentError) end - it "leaves the current version at nil if the package is not installed" do - @new_resource.package_name("no-such-gem-should-exist-with-this-name") - @provider.load_current_resource - @provider.current_resource.version.should be_nil + it "converts the new resource into a gem dependency" do + @provider.gem_dependency.should == Gem::Dependency.new('rspec-core', @spec_version) + @new_resource.version('~> 1.2.0') + @provider.gem_dependency.should == Gem::Dependency.new('rspec-core', '~> 1.2.0') end - end + describe "when determining the currently installed version" do - describe "when determining the candidate version to install" do + it "sets the current version to the version specified by the new resource if that version is installed" do + @provider.load_current_resource + @provider.current_resource.version.should == @spec_version + end - it "does not query for available versions when the current version is the target version" do - @provider.current_resource = @new_resource.dup - @provider.candidate_version.should be_nil - end + it "sets the current version to the highest installed version if the requested version is not installed" do + @new_resource.version('9000.0.2') + @provider.load_current_resource + @provider.current_resource.version.should == @spec_version + end - it "determines the candidate version by querying the remote gem servers" do - @new_resource.source('http://mygems.example.com') - version = Gem::Version.new(@spec_version) - @provider.gem_env.should_receive(:candidate_version_from_remote). - with(Gem::Dependency.new('rspec-core', @spec_version), "http://mygems.example.com"). - and_return(version) - @provider.candidate_version.should == @spec_version - end + it "leaves the current version at nil if the package is not installed" do + @new_resource.package_name("no-such-gem-should-exist-with-this-name") + @provider.load_current_resource + @provider.current_resource.version.should be_nil + end - it "parses the gem's specification if the requested source is a file" do - @new_resource.package_name('chef-integration-test') - @new_resource.version('>= 0') - @new_resource.source(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') - @provider.candidate_version.should == '0.1.0' end - end - - describe "when installing a gem" do - before do - @current_resource = Chef::Resource::GemPackage.new('rspec-core') - @provider.current_resource = @current_resource - @gem_dep = Gem::Dependency.new('rspec-core', @spec_version) - @provider.stub(:load_current_resource) - end + describe "when determining the candidate version to install" do - describe "in the current gem environment" do - it "installs the gem via the gems api when no explicit options are used" do - @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => nil) - @provider.action_install.should be_true + it "does not query for available versions when the current version is the target version" do + @provider.current_resource = @new_resource.dup + @provider.candidate_version.should be_nil end - it "installs the gem via the gems api when a remote source is provided" do - @new_resource.source('http://gems.example.org') - sources = ['http://gems.example.org'] - @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => sources) - @provider.action_install.should be_true + it "determines the candidate version by querying the remote gem servers" do + @new_resource.source('http://mygems.example.com') + version = Gem::Version.new(@spec_version) + @provider.gem_env.should_receive(:candidate_version_from_remote). + with(Gem::Dependency.new('rspec-core', @spec_version), "http://mygems.example.com"). + and_return(version) + @provider.candidate_version.should == @spec_version end - it "installs the gem from file via the gems api when no explicit options are used" do + it "parses the gem's specification if the requested source is a file" do + @new_resource.package_name('chef-integration-test') + @new_resource.version('>= 0') @new_resource.source(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') - @provider.gem_env.should_receive(:install).with(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') - @provider.action_install.should be_true + @provider.candidate_version.should == '0.1.0' end - it "installs the gem from file via the gems api when the package is a path and the source is nil" do - @new_resource = Chef::Resource::GemPackage.new(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') - @provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context) + end + + describe "when installing a gem" do + before do + @current_resource = Chef::Resource::GemPackage.new('rspec-core') @provider.current_resource = @current_resource - @new_resource.source.should == CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem' - @provider.gem_env.should_receive(:install).with(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') - @provider.action_install.should be_true + @gem_dep = Gem::Dependency.new('rspec-core', @spec_version) + @provider.stub(:load_current_resource) end - # this catches 'gem_package "foo"' when "./foo" is a file in the cwd, and instead of installing './foo' it fetches the remote gem - it "installs the gem via the gems api, when the package has no file separator characters in it, but a matching file exists in cwd" do - ::File.stub(:exists?).and_return(true) - @new_resource.package_name('rspec-core') - @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => nil) - @provider.action_install.should be_true - end + describe "in the current gem environment" do + it "installs the gem via the gems api when no explicit options are used" do + @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => nil) + @provider.action_install.should be_true + end - it "installs the gem by shelling out when options are provided as a String" do - @new_resource.options('-i /alt/install/location') - expected ="gem install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\" -i /alt/install/location" - @provider.should_receive(:shell_out!).with(expected, :env => nil) - @provider.action_install.should be_true - end + it "installs the gem via the gems api when a remote source is provided" do + @new_resource.source('http://gems.example.org') + sources = ['http://gems.example.org'] + @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => sources) + @provider.action_install.should be_true + end - it "installs the gem via the gems api when options are given as a Hash" do - @new_resource.options(:install_dir => '/alt/install/location') - @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => nil, :install_dir => '/alt/install/location') - @provider.action_install.should be_true - end + it "installs the gem from file via the gems api when no explicit options are used" do + @new_resource.source(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') + @provider.gem_env.should_receive(:install).with(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') + @provider.action_install.should be_true + end - describe "at a specific version" do - before do - @gem_dep = Gem::Dependency.new('rspec-core', @spec_version) + it "installs the gem from file via the gems api when the package is a path and the source is nil" do + @new_resource = Chef::Resource::GemPackage.new(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') + @provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context) + @provider.current_resource = @current_resource + @new_resource.source.should == CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem' + @provider.gem_env.should_receive(:install).with(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') + @provider.action_install.should be_true end - it "installs the gem via the gems api" do + # this catches 'gem_package "foo"' when "./foo" is a file in the cwd, and instead of installing './foo' it fetches the remote gem + it "installs the gem via the gems api, when the package has no file separator characters in it, but a matching file exists in cwd" do + ::File.stub(:exists?).and_return(true) + @new_resource.package_name('rspec-core') @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => nil) @provider.action_install.should be_true end - end - describe "at version specified with comparison operator" do - it "skips install if current version satisifies requested version" do - @current_resource.stub(:version).and_return("2.3.3") - @new_resource.stub(:version).and_return(">=2.3.0") - @provider.gem_env.should_not_receive(:install) - @provider.action_install + it "installs the gem by shelling out when options are provided as a String" do + @new_resource.options('-i /alt/install/location') + expected ="gem install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\" -i /alt/install/location" + @provider.should_receive(:shell_out!).with(expected, :env => nil) + @provider.action_install.should be_true + end + + it "installs the gem via the gems api when options are given as a Hash" do + @new_resource.options(:install_dir => '/alt/install/location') + @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => nil, :install_dir => '/alt/install/location') + @provider.action_install.should be_true end - it "allows user to specify gem version with fuzzy operator" do - @current_resource.stub(:version).and_return("2.3.3") - @new_resource.stub(:version).and_return("~>2.3.0") + describe "at a specific version" do + before do + @gem_dep = Gem::Dependency.new('rspec-core', @spec_version) + end - @provider.gem_env.should_not_receive(:install) - @provider.action_install + it "installs the gem via the gems api" do + @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => nil) + @provider.action_install.should be_true + end + end + describe "at version specified with comparison operator" do + it "skips install if current version satisifies requested version" do + @current_resource.stub(:version).and_return("2.3.3") + @new_resource.stub(:version).and_return(">=2.3.0") + + @provider.gem_env.should_not_receive(:install) + @provider.action_install + end + + it "allows user to specify gem version with fuzzy operator" do + @current_resource.stub(:version).and_return("2.3.3") + @new_resource.stub(:version).and_return("~>2.3.0") + + @provider.gem_env.should_not_receive(:install) + @provider.action_install + end end end - end - describe "in an alternate gem environment" do - it "installs the gem by shelling out to gem install" do - @new_resource.gem_binary('/usr/weird/bin/gem') - @provider.should_receive(:shell_out!).with("/usr/weird/bin/gem install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\"", :env=>nil) - @provider.action_install.should be_true - end + describe "in an alternate gem environment" do + it "installs the gem by shelling out to gem install" do + @new_resource.gem_binary('/usr/weird/bin/gem') + @provider.should_receive(:shell_out!).with("/usr/weird/bin/gem install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\"", :env=>nil) + @provider.action_install.should be_true + end - it "installs the gem from file by shelling out to gem install" do - @new_resource.gem_binary('/usr/weird/bin/gem') - @new_resource.source(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') - @new_resource.version('>= 0') - @provider.should_receive(:shell_out!).with("/usr/weird/bin/gem install #{CHEF_SPEC_DATA}/gems/chef-integration-test-0.1.0.gem -q --no-rdoc --no-ri -v \">= 0\"", :env=>nil) - @provider.action_install.should be_true - end + it "installs the gem from file by shelling out to gem install" do + @new_resource.gem_binary('/usr/weird/bin/gem') + @new_resource.source(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') + @new_resource.version('>= 0') + @provider.should_receive(:shell_out!).with("/usr/weird/bin/gem install #{CHEF_SPEC_DATA}/gems/chef-integration-test-0.1.0.gem -q --no-rdoc --no-ri -v \">= 0\"", :env=>nil) + @provider.action_install.should be_true + end - it "installs the gem from file by shelling out to gem install when the package is a path and the source is nil" do - @new_resource = Chef::Resource::GemPackage.new(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') - @provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context) - @provider.current_resource = @current_resource - @new_resource.gem_binary('/usr/weird/bin/gem') - @new_resource.version('>= 0') - @new_resource.source.should == CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem' - @provider.should_receive(:shell_out!).with("/usr/weird/bin/gem install #{CHEF_SPEC_DATA}/gems/chef-integration-test-0.1.0.gem -q --no-rdoc --no-ri -v \">= 0\"", :env=>nil) - @provider.action_install.should be_true + it "installs the gem from file by shelling out to gem install when the package is a path and the source is nil" do + @new_resource = Chef::Resource::GemPackage.new(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') + @provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context) + @provider.current_resource = @current_resource + @new_resource.gem_binary('/usr/weird/bin/gem') + @new_resource.version('>= 0') + @new_resource.source.should == CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem' + @provider.should_receive(:shell_out!).with("/usr/weird/bin/gem install #{CHEF_SPEC_DATA}/gems/chef-integration-test-0.1.0.gem -q --no-rdoc --no-ri -v \">= 0\"", :env=>nil) + @provider.action_install.should be_true + end end - end - end - - describe "when uninstalling a gem" do - before do - @new_resource = Chef::Resource::GemPackage.new("rspec") - @current_resource = @new_resource.dup - @current_resource.version('1.2.3') - @provider.new_resource = @new_resource - @provider.current_resource = @current_resource end - describe "in the current gem environment" do - it "uninstalls via the api when no explicit options are used" do - # pre-reqs for action_remove to actually remove the package: - @provider.new_resource.version.should be_nil - @provider.current_resource.version.should_not be_nil - # the behavior we're testing: - @provider.gem_env.should_receive(:uninstall).with('rspec', nil) - @provider.action_remove + describe "when uninstalling a gem" do + before do + @new_resource = Chef::Resource::GemPackage.new("rspec") + @current_resource = @new_resource.dup + @current_resource.version('1.2.3') + @provider.new_resource = @new_resource + @provider.current_resource = @current_resource end - it "uninstalls via the api when options are given as a Hash" do - # pre-reqs for action_remove to actually remove the package: - @provider.new_resource.version.should be_nil - @provider.current_resource.version.should_not be_nil - # the behavior we're testing: - @new_resource.options(:install_dir => '/alt/install/location') - @provider.gem_env.should_receive(:uninstall).with('rspec', nil, :install_dir => '/alt/install/location') - @provider.action_remove - end + describe "in the current gem environment" do + it "uninstalls via the api when no explicit options are used" do + # pre-reqs for action_remove to actually remove the package: + @provider.new_resource.version.should be_nil + @provider.current_resource.version.should_not be_nil + # the behavior we're testing: + @provider.gem_env.should_receive(:uninstall).with('rspec', nil) + @provider.action_remove + end - it "uninstalls via the gem command when options are given as a String" do - @new_resource.options('-i /alt/install/location') - @provider.should_receive(:shell_out!).with("gem uninstall rspec -q -x -I -a -i /alt/install/location", :env=>nil) - @provider.action_remove - end + it "uninstalls via the api when options are given as a Hash" do + # pre-reqs for action_remove to actually remove the package: + @provider.new_resource.version.should be_nil + @provider.current_resource.version.should_not be_nil + # the behavior we're testing: + @new_resource.options(:install_dir => '/alt/install/location') + @provider.gem_env.should_receive(:uninstall).with('rspec', nil, :install_dir => '/alt/install/location') + @provider.action_remove + end + + it "uninstalls via the gem command when options are given as a String" do + @new_resource.options('-i /alt/install/location') + @provider.should_receive(:shell_out!).with("gem uninstall rspec -q -x -I -a -i /alt/install/location", :env=>nil) + @provider.action_remove + end - it "uninstalls a specific version of a gem when a version is provided" do - @new_resource.version('1.2.3') - @provider.gem_env.should_receive(:uninstall).with('rspec', '1.2.3') - @provider.action_remove + it "uninstalls a specific version of a gem when a version is provided" do + @new_resource.version('1.2.3') + @provider.gem_env.should_receive(:uninstall).with('rspec', '1.2.3') + @provider.action_remove + end end - end - describe "in an alternate gem environment" do - it "uninstalls via the gem command" do - @new_resource.gem_binary('/usr/weird/bin/gem') - @provider.should_receive(:shell_out!).with("/usr/weird/bin/gem uninstall rspec -q -x -I -a", :env=>nil) - @provider.action_remove + describe "in an alternate gem environment" do + it "uninstalls via the gem command" do + @new_resource.gem_binary('/usr/weird/bin/gem') + @provider.should_receive(:shell_out!).with("/usr/weird/bin/gem uninstall rspec -q -x -I -a", :env=>nil) + @provider.action_remove + end end end end end - diff --git a/spec/unit/provider/remote_file/cache_control_data_spec.rb b/spec/unit/provider/remote_file/cache_control_data_spec.rb index 8e396b1b40..8a849d9d7d 100644 --- a/spec/unit/provider/remote_file/cache_control_data_spec.rb +++ b/spec/unit/provider/remote_file/cache_control_data_spec.rb @@ -85,7 +85,7 @@ describe Chef::Provider::RemoteFile::CacheControlData do cache["etag"] = etag cache["mtime"] = mtime cache["checksum"] = last_fetched_checksum - cache.to_json + Chef::JSONCompat.to_json(cache) end before do diff --git a/spec/unit/recipe_spec.rb b/spec/unit/recipe_spec.rb index c795ba3788..5b6f3ea55b 100644 --- a/spec/unit/recipe_spec.rb +++ b/spec/unit/recipe_spec.rb @@ -361,6 +361,15 @@ describe Chef::Recipe do end end + describe "handle exec calls" do + it "should raise ResourceNotFound error if exec is used" do + code = <<-CODE + exec 'do_not_try_to_exec' + CODE + lambda { recipe.instance_eval(code) }.should raise_error(Chef::Exceptions::ResourceNotFound) + end + end + describe "from_file" do it "should load a resource from a ruby file" do recipe.from_file(File.join(CHEF_SPEC_DATA, "recipes", "test.rb")) diff --git a/spec/unit/resource/dsc_script_spec.rb b/spec/unit/resource/dsc_script_spec.rb index cbd502a61c..eb9d19e553 100644 --- a/spec/unit/resource/dsc_script_spec.rb +++ b/spec/unit/resource/dsc_script_spec.rb @@ -95,33 +95,4 @@ describe Chef::Resource::DscScript do expect { dsc_test_resource.configuration_data_script(configuration_data_script) }.to raise_error(ArgumentError) end end - - context 'when Powershell does not supported Dsc' do - ['1.0', '2.0', '3.0'].each do |version| - it "raises an exception for powershell version '#{version}'" do - node = Chef::Node.new - node.automatic[:languages][:powershell][:version] = version - empty_events = Chef::EventDispatch::Dispatcher.new - dsc_test_run_context = Chef::RunContext.new(node, {}, empty_events) - - expect { - Chef::Resource::DscScript.new(dsc_test_resource_name, dsc_test_run_context) - }.to raise_error(Chef::Exceptions::NoProviderAvailable) - end - end - end - - context 'when Powershell is not present' do - let (:dsc_test_run_context) { - node = Chef::Node.new - empty_events = Chef::EventDispatch::Dispatcher.new - dsc_test_run_context = Chef::RunContext.new(node, {}, empty_events) - } - - it 'raises an exception if powershell is not present' do - expect { - Chef::Resource::DscScript.new(dsc_test_resource_name, dsc_test_run_context) - }.to raise_error(Chef::Exceptions::NoProviderAvailable) - end - end end diff --git a/spec/unit/resource/freebsd_package_spec.rb b/spec/unit/resource/freebsd_package_spec.rb index ae12abac6e..04a6962270 100644 --- a/spec/unit/resource/freebsd_package_spec.rb +++ b/spec/unit/resource/freebsd_package_spec.rb @@ -57,7 +57,7 @@ describe Chef::Resource::FreebsdPackage do describe "if __Freebsd_version is greater than or equal to 1000017" do it "should be Freebsd::Pkgng" do [1000017, 1000018, 1000500, 1001001, 1100000].each do |__freebsd_version| - @node.normal[:os_version] = __freebsd_version + @node.automatic_attrs[:os_version] = __freebsd_version @resource.after_created @resource.provider.should == Chef::Provider::Package::Freebsd::Pkgng end @@ -79,8 +79,7 @@ describe Chef::Resource::FreebsdPackage do @resource.stub(:shell_out!).with("make -V WITH_PKGNG", :env => nil).and_return(pkg_enabled) [1000016, 1000000, 901503, 902506, 802511].each do |__freebsd_version| - @node[:os_version] == __freebsd_version - @node.normal[:os_version] = __freebsd_version + @node.automatic_attrs[:os_version] = __freebsd_version @resource.after_created @resource.provider.should == Chef::Provider::Package::Freebsd::Pkg end diff --git a/spec/unit/resource_collection_spec.rb b/spec/unit/resource_collection_spec.rb index eddd92e098..cf119f1ab0 100644 --- a/spec/unit/resource_collection_spec.rb +++ b/spec/unit/resource_collection_spec.rb @@ -278,12 +278,20 @@ describe Chef::ResourceCollection do json.should =~ /json_class/ json.should =~ /instance_vars/ end + + include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + let(:jsonable) { @rc } + end end describe "self.from_json" do - it "should deserialize itself from json" do + it "should not respond to this method" do + expect(@rc.respond_to?(:from_json)).to eq(false) + end + + it "should convert from json using the CHEF::JSONCompat library" do @rc << @resource - json = @rc.to_json + json = Chef::JSONCompat.to_json(@rc) s_rc = Chef::JSONCompat.from_json(json) s_rc.should be_a_kind_of(Chef::ResourceCollection) s_rc[0].name.should eql(@resource.name) diff --git a/spec/unit/resource_reporter_spec.rb b/spec/unit/resource_reporter_spec.rb index fe6a895b5a..1a89cbdce1 100644 --- a/spec/unit/resource_reporter_spec.rb +++ b/spec/unit/resource_reporter_spec.rb @@ -421,7 +421,7 @@ describe Chef::ResourceReporter do it "includes the run_list" do @report.should have_key("run_list") - @report["run_list"].should == @run_status.node.run_list.to_json + @report["run_list"].should == Chef::JSONCompat.to_json(@run_status.node.run_list) end it "includes the end_time" do @@ -484,7 +484,7 @@ describe Chef::ResourceReporter do it "includes the exception trace in the event data" do @report["data"]["exception"].should have_key("backtrace") - @report["data"]["exception"]["backtrace"].should == @backtrace.to_json + @report["data"]["exception"]["backtrace"].should == Chef::JSONCompat.to_json(@backtrace) end it "includes the error inspector output in the event data" do @@ -701,7 +701,7 @@ describe Chef::ResourceReporter do }) data_stream = Zlib::GzipReader.new(StringIO.new(data)) data = data_stream.read - data.should eq(@expected_data.to_json) + data.should eq(Chef::JSONCompat.to_json(@expected_data)) response end diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb index e85633eaab..692345c943 100644 --- a/spec/unit/resource_spec.rb +++ b/spec/unit/resource_spec.rb @@ -336,6 +336,10 @@ describe Chef::Resource do json.should =~ /json_class/ json.should =~ /instance_vars/ end + + include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + let(:jsonable) { @resource } + end end describe "to_hash" do @@ -354,7 +358,7 @@ describe Chef::Resource do describe "self.json_create" do it "should deserialize itself from json" do - json = @resource.to_json + json = Chef::JSONCompat.to_json(@resource) serialized_node = Chef::JSONCompat.from_json(json) serialized_node.should be_a_kind_of(Chef::Resource) serialized_node.name.should eql(@resource.name) diff --git a/spec/unit/role_spec.rb b/spec/unit/role_spec.rb index f3fa7e1868..1a108c4247 100644 --- a/spec/unit/role_spec.rb +++ b/spec/unit/role_spec.rb @@ -216,6 +216,10 @@ describe Chef::Role do end end + + include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + let(:jsonable) { @role } + end end describe "when created from JSON", :json => true do diff --git a/spec/unit/run_list_spec.rb b/spec/unit/run_list_spec.rb index 220e4ea4a6..cc7e29af0f 100644 --- a/spec/unit/run_list_spec.rb +++ b/spec/unit/run_list_spec.rb @@ -304,7 +304,11 @@ describe Chef::RunList do end it "converts to json by converting its array form" do - @run_list.to_json.should == ["recipe[nagios::client]", "role[production]", "recipe[apache2]"].to_json + Chef::JSONCompat.to_json(@run_list).should == Chef::JSONCompat.to_json(["recipe[nagios::client]", "role[production]", "recipe[apache2]"]) + end + + include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + let(:jsonable) { @run_list } end end diff --git a/spec/unit/user_spec.rb b/spec/unit/user_spec.rb index 08bde33d7b..2f2299c5bd 100644 --- a/spec/unit/user_spec.rb +++ b/spec/unit/user_spec.rb @@ -154,6 +154,10 @@ describe Chef::User do it "does not include the password if not present" do @json.should_not include("password") end + + include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + let(:jsonable) { @user } + end end describe "when deserializing from JSON" do @@ -163,7 +167,7 @@ describe Chef::User do "private_key" => "pandas", "password" => "password", "admin" => true } - @user = Chef::User.from_json(user.to_json) + @user = Chef::User.from_json(Chef::JSONCompat.to_json(user)) end it "should deserialize to a Chef::User object" do |