diff options
107 files changed, 776 insertions, 467 deletions
diff --git a/.travis.yml b/.travis.yml index 5c45a016b6..f1146a4aaa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,6 @@ before_install: matrix: include: - - rvm: 1.8.7-p374 - rvm: 1.9.3 - rvm: 2.0.0 - rvm: 2.1.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 13ecaf592c..77df6610b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased: +* Removed dependencies on the 'json' gem, replaced with ffi-yajl. Use Chef::JSONCompat library for parsing and printing. +* [Issue 2027](https://github.com/opscode/chef/issues/2027) Allow recipe using `dsc_script` opportunity to install Powershell 4 or higher +* [Issue 2169](https://github.com/opscode/chef/issues/2169) Attempt to converge DSC configurations with resources that do not correctly support what-if + ## Last Release: 11.16.4 * Windows omnibus installer security updates for redistributed bash.exe / sh.exe @@ -13,7 +17,8 @@ ## Release: 11.16.2 -* This is a packaging-only release there are no code changes +* [**Phil Dibowitz**](https://github.com/jaymzh): + Fix a regression in whyrun_safe_ruby_block. ## Release: 11.16.0 diff --git a/chef.gemspec b/chef.gemspec index 7c53618a51..36412aa679 100644 --- a/chef.gemspec +++ b/chef.gemspec @@ -29,7 +29,7 @@ Gem::Specification.new do |s| # it's the version I had when I tested. s.add_dependency "mime-types", "~> 1.16" - s.add_dependency "ffi-yajl", "~> 1.0" + 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. @@ -37,7 +37,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", "~> 2.1", ">= 2.1.4" + s.add_dependency "chef-zero", "~> 2.2", ">= 2.2.1" s.add_dependency "pry", "~> 0.9" s.add_dependency 'plist', '~> 3.1.0' diff --git a/lib/chef/api_client.rb b/lib/chef/api_client.rb index 7b7fd99ff7..334fb23f38 100644 --- a/lib/chef/api_client.rb +++ b/lib/chef/api_client.rb @@ -121,7 +121,7 @@ class Chef # # @return [String] the JSON string. def to_json(*a) - to_hash.to_json(*a) + Chef::JSONCompat.to_json(to_hash, *a) end def self.json_create(o) diff --git a/lib/chef/chef_fs/chef_fs_data_store.rb b/lib/chef/chef_fs/chef_fs_data_store.rb index b84fc1945d..a88bf0c256 100644 --- a/lib/chef/chef_fs/chef_fs_data_store.rb +++ b/lib/chef/chef_fs/chef_fs_data_store.rb @@ -23,6 +23,7 @@ require 'chef/chef_fs/file_pattern' require 'chef/chef_fs/file_system' require 'chef/chef_fs/file_system/not_found_error' require 'chef/chef_fs/file_system/memory_root' +require 'chef/json_compat' require 'fileutils' class Chef @@ -114,7 +115,7 @@ class Chef end end end - JSON.pretty_generate(result) + Chef::JSONCompat.to_json_pretty(result) else begin @@ -269,7 +270,7 @@ class Chef # Create a little Chef::ChefFS memory filesystem with the data cookbook_fs = Chef::ChefFS::FileSystem::MemoryRoot.new('uploading') - cookbook = JSON.parse(data, :create_additions => false) + cookbook = Chef::JSONCompat.parse(data, :create_additions => false) cookbook.each_pair do |key, value| if value.is_a?(Array) value.each do |file| diff --git a/lib/chef/chef_fs/command_line.rb b/lib/chef/chef_fs/command_line.rb index 967c59ecae..5fa770089b 100644 --- a/lib/chef/chef_fs/command_line.rb +++ b/lib/chef/chef_fs/command_line.rb @@ -19,6 +19,7 @@ require 'chef/chef_fs/file_system' require 'chef/chef_fs/file_system/operation_failed_error' require 'chef/chef_fs/file_system/operation_not_allowed_error' +require 'chef/json_compat' require 'chef/util/diff' class Chef @@ -251,9 +252,9 @@ class Chef end def self.canonicalize_json(json_text) - parsed_json = JSON.parse(json_text, :create_additions => false) + parsed_json = Chef::JSONCompat.parse(json_text, :create_additions => false) 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/acl_entry.rb b/lib/chef/chef_fs/file_system/acl_entry.rb index 8edc02d5c5..1659b557ba 100644 --- a/lib/chef/chef_fs/file_system/acl_entry.rb +++ b/lib/chef/chef_fs/file_system/acl_entry.rb @@ -20,6 +20,7 @@ require 'chef/chef_fs/file_system/rest_list_entry' require 'chef/chef_fs/file_system/not_found_error' require 'chef/chef_fs/file_system/operation_not_allowed_error' require 'chef/chef_fs/file_system/operation_failed_error' +require 'chef/json_compat' class Chef module ChefFS @@ -37,7 +38,7 @@ class Chef def write(file_contents) # ACL writes are fun. - acls = data_handler.normalize(JSON.parse(file_contents, :create_additions => false), self) + acls = data_handler.normalize(Chef::JSONCompat.parse(file_contents, :create_additions => false), self) PERMISSIONS.each do |permission| begin rest.put("#{api_path}/#{permission}", { permission => acls[permission] }) diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb index 3d3f58201e..d22d11b0f3 100644 --- a/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb +++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb @@ -19,6 +19,7 @@ require 'chef/chef_fs/file_system/file_system_entry' require 'chef/chef_fs/file_system/not_found_error' +require 'chef/json_compat' class Chef module ChefFS @@ -41,7 +42,7 @@ class Chef def chef_object begin - return data_handler.chef_object(JSON.parse(read, :create_additions => false)) + return data_handler.chef_object(Chef::JSONCompat.parse(read, :create_additions => false)) rescue Chef::Log.error("Could not read #{path_for_printing} into a Chef object: #{$!}") end diff --git a/lib/chef/chef_fs/file_system/rest_list_dir.rb b/lib/chef/chef_fs/file_system/rest_list_dir.rb index b7ee51d284..273caa3478 100644 --- a/lib/chef/chef_fs/file_system/rest_list_dir.rb +++ b/lib/chef/chef_fs/file_system/rest_list_dir.rb @@ -19,6 +19,7 @@ require 'chef/chef_fs/file_system/base_fs_dir' require 'chef/chef_fs/file_system/rest_list_entry' require 'chef/chef_fs/file_system/not_found_error' +require 'chef/json_compat' class Chef module ChefFS @@ -61,8 +62,8 @@ class Chef def create_child(name, file_contents) begin - object = JSON.parse(file_contents, :create_additions => false) - rescue JSON::ParserError => e + object = Chef::JSONCompat.parse(file_contents, :create_additions => false) + rescue Chef::Exceptions::JSON::ParseError => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e), "Parse error reading JSON creating child '#{name}': #{e}" end 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 0d5557de1d..345fab63be 100644 --- a/lib/chef/chef_fs/file_system/rest_list_entry.rb +++ b/lib/chef/chef_fs/file_system/rest_list_entry.rb @@ -19,6 +19,7 @@ require 'chef/chef_fs/file_system/base_fs_object' require 'chef/chef_fs/file_system/not_found_error' require 'chef/chef_fs/file_system/operation_failed_error' +require 'chef/json_compat' require 'chef/role' require 'chef/node' @@ -128,8 +129,8 @@ class Chef value = minimize_value(value) value_json = Chef::JSONCompat.to_json_pretty(value) begin - other_value = JSON.parse(other_value_json, :create_additions => false) - rescue JSON::ParserError => e + other_value = Chef::JSONCompat.parse(other_value_json, :create_additions => false) + rescue Chef::Exceptions::JSON::ParseError => e Chef::Log.warn("Parse error reading #{other.path_for_printing} as JSON: #{e}") return [ nil, value_json, other_value_json ] end @@ -145,8 +146,8 @@ class Chef def write(file_contents) begin - object = JSON.parse(file_contents, :create_additions => false) - rescue JSON::ParserError => e + object = Chef::JSONCompat.parse(file_contents, :create_additions => false) + rescue Chef::Exceptions::JSON::ParseError => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "Parse error reading JSON: #{e}" end diff --git a/lib/chef/config_fetcher.rb b/lib/chef/config_fetcher.rb index c1fd262656..1d0693eaa2 100644 --- a/lib/chef/config_fetcher.rb +++ b/lib/chef/config_fetcher.rb @@ -18,7 +18,7 @@ class Chef config_data = read_config begin Chef::JSONCompat.from_json(config_data) - rescue FFI_Yajl::ParseError => error + rescue Chef::Exceptions::JSON::ParseError => error Chef::Application.fatal!("Could not parse the provided JSON file (#{config_location}): " + error.message, 2) end end diff --git a/lib/chef/cookbook/cookbook_version_loader.rb b/lib/chef/cookbook/cookbook_version_loader.rb index 4e92b74ae9..fac8c80993 100644 --- a/lib/chef/cookbook/cookbook_version_loader.rb +++ b/lib/chef/cookbook/cookbook_version_loader.rb @@ -170,7 +170,7 @@ class Chef def apply_ruby_metadata(file) begin @metadata.from_file(file) - rescue JSON::ParserError + rescue Chef::Exceptions::JSON::ParseError Chef::Log.error("Error evaluating metadata.rb for #@cookbook_name in " + file) raise end @@ -179,7 +179,7 @@ class Chef def apply_json_metadata(file) begin @metadata.from_json(IO.read(file)) - rescue JSON::ParserError + rescue Chef::Exceptions::JSON::ParseError Chef::Log.error("Couldn't parse cookbook metadata JSON for #@cookbook_name in " + file) raise end @@ -189,7 +189,7 @@ class Chef begin data = Chef::JSONCompat.from_json(IO.read(file), :create_additions => false) @metadata.from_hash(data['metadata']) - rescue JSON::ParserError + rescue Chef::Exceptions::JSON::ParseError Chef::Log.error("Couldn't parse cookbook metadata JSON for #@cookbook_name in " + file) raise end @@ -200,7 +200,7 @@ class Chef begin data = Chef::JSONCompat.from_json(IO.read(uploaded_cookbook_version_file), :create_additions => false) @frozen = data['frozen?'] - rescue JSON::ParserError + rescue Chef::Exceptions::JSON::ParseError Chef::Log.error("Couldn't parse cookbook metadata JSON for #@cookbook_name in #{uploaded_cookbook_version_file}") raise end diff --git a/lib/chef/cookbook/metadata.rb b/lib/chef/cookbook/metadata.rb index 7da1ae70de..21ab72e1cd 100644 --- a/lib/chef/cookbook/metadata.rb +++ b/lib/chef/cookbook/metadata.rb @@ -441,7 +441,7 @@ class Chef end def to_json(*a) - self.to_hash.to_json(*a) + Chef::JSONCompat.to_json(self.to_hash, *a) end def self.from_hash(o) diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb index 3d8b9fb908..f9775c4d8d 100644 --- a/lib/chef/cookbook_version.rb +++ b/lib/chef/cookbook_version.rb @@ -459,7 +459,7 @@ class Chef def to_json(*a) result = self.to_hash result['json_class'] = self.class.name - result.to_json(*a) + Chef::JSONCompat.to_json(result, *a) end def self.json_create(o) @@ -469,7 +469,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 diff --git a/lib/chef/data_bag.rb b/lib/chef/data_bag.rb index 639d71a74d..f50aa93d2d 100644 --- a/lib/chef/data_bag.rb +++ b/lib/chef/data_bag.rb @@ -63,7 +63,7 @@ class Chef # Serialize this object as a hash def to_json(*a) - to_hash.to_json(*a) + Chef::JSONCompat.to_json(to_hash, *a) end def chef_server_rest diff --git a/lib/chef/data_bag_item.rb b/lib/chef/data_bag_item.rb index 07dd15a1dc..3772f9ea1c 100644 --- a/lib/chef/data_bag_item.rb +++ b/lib/chef/data_bag_item.rb @@ -118,7 +118,7 @@ class Chef "data_bag" => self.data_bag, "raw_data" => self.raw_data } - result.to_json(*a) + Chef::JSONCompat.to_json(result, *a) end def self.from_hash(h) diff --git a/lib/chef/encrypted_data_bag_item/decryptor.rb b/lib/chef/encrypted_data_bag_item/decryptor.rb index 69b8d62e3b..503a8f3099 100644 --- a/lib/chef/encrypted_data_bag_item/decryptor.rb +++ b/lib/chef/encrypted_data_bag_item/decryptor.rb @@ -17,10 +17,10 @@ # require 'yaml' -require 'ffi_yajl' require 'openssl' require 'base64' require 'digest/sha2' +require 'chef/json_compat' require 'chef/encrypted_data_bag_item' require 'chef/encrypted_data_bag_item/unsupported_encrypted_data_bag_item_format' require 'chef/encrypted_data_bag_item/unacceptable_encrypted_data_bag_item_format' @@ -121,8 +121,8 @@ class Chef::EncryptedDataBagItem end def for_decrypted_item - FFI_Yajl::Parser.parse(decrypted_data)["json_wrapper"] - rescue FFI_Yajl::ParseError + Chef::JSONCompat.parse(decrypted_data)["json_wrapper"] + rescue Chef::Exceptions::JSON::ParseError # convert to a DecryptionFailure error because the most likely scenario # here is that the decryption step was unsuccessful but returned bad # data rather than raising an error. diff --git a/lib/chef/environment.rb b/lib/chef/environment.rb index 5c719ca285..3620ee0b7c 100644 --- a/lib/chef/environment.rb +++ b/lib/chef/environment.rb @@ -129,7 +129,7 @@ class Chef end def to_json(*a) - to_hash.to_json(*a) + Chef::JSONCompat.to_json(to_hash, *a) end def update_from!(o) diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb index 9ea40f71f6..2e7a92ab6f 100644 --- a/lib/chef/exceptions.rb +++ b/lib/chef/exceptions.rb @@ -267,7 +267,7 @@ class Chef "non_existent_cookbooks" => non_existent_cookbooks, "cookbooks_with_no_versions" => cookbooks_with_no_matching_versions } - result.to_json(*a) + Chef::JSONCompat.to_json(result, *a) end end @@ -302,7 +302,7 @@ class Chef "non_existent_cookbooks" => non_existent_cookbooks, "most_constrained_cookbooks" => most_constrained_cookbooks } - result.to_json(*a) + Chef::JSONCompat.to_json(result, *a) end end @@ -337,5 +337,11 @@ class Chef end class BadProxyURI < RuntimeError; end + + # Raised by Chef::JSONCompat + class JSON + class EncodeError < RuntimeError; end + class ParseError < RuntimeError; end + end end end diff --git a/lib/chef/json_compat.rb b/lib/chef/json_compat.rb index 2dbb607d9b..9db358b5b9 100644 --- a/lib/chef/json_compat.rb +++ b/lib/chef/json_compat.rb @@ -18,7 +18,9 @@ # Wrapper class for interacting with JSON. require 'ffi_yajl' -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 @@ -40,15 +42,24 @@ class Chef class <<self + # API to use to avoid create_addtions + def parse(source, opts = {}) + begin + FFI_Yajl::Parser.parse(source, opts) + rescue FFI_Yajl::ParseError => e + raise Chef::Exceptions::JSON::ParseError, e.message + end + end + # Just call the JSON gem's parse method with a modified :max_nesting field def from_json(source, opts = {}) - obj = ::FFI_Yajl::Parser.parse(source) + obj = parse(source, opts) # JSON gem requires top level object to be a Hash or Array (otherwise # you get the "must contain two octets" error). Yajl doesn't impose the # same limitation. For compatibility, we re-impose this condition. unless obj.kind_of?(Hash) or obj.kind_of?(Array) - raise JSON::ParserError, "Top level JSON object must be a Hash or Array. (actual: #{obj.class})" + raise Chef::Exceptions::JSON::ParseError, "Top level JSON object must be a Hash or Array. (actual: #{obj.class})" end # The old default in the json gem (which we are mimicing because we @@ -66,17 +77,17 @@ class Chef # to an instance of Chef classes if desired. def map_to_rb_obj(json_obj) case json_obj - when Hash - mapped_hash = map_hash_to_rb_obj(json_obj) - if json_obj.has_key?(JSON_CLASS) && (class_to_inflate = class_for_json_class(json_obj[JSON_CLASS])) - class_to_inflate.json_create(mapped_hash) + when Hash + mapped_hash = map_hash_to_rb_obj(json_obj) + if json_obj.has_key?(JSON_CLASS) && (class_to_inflate = class_for_json_class(json_obj[JSON_CLASS])) + class_to_inflate.json_create(mapped_hash) + else + mapped_hash + end + when Array + json_obj.map {|e| map_to_rb_obj(e) } else - mapped_hash - end - when Array - json_obj.map {|e| map_to_rb_obj(e) } - else - json_obj + json_obj end end @@ -88,52 +99,60 @@ class Chef end def to_json(obj, opts = nil) - obj.to_json(opts) + begin + FFI_Yajl::Encoder.encode(obj, opts) + rescue FFI_Yajl::EncodeError => e + raise Chef::Exceptions::JSON::EncodeError, e.message + end end def to_json_pretty(obj, opts = nil) - ::JSON.pretty_generate(obj, opts) + opts ||= {} + options_map = {} + options_map[:pretty] = true + options_map[:indent] = opts[:indent] if opts.has_key?(:indent) + to_json(obj, options_map).chomp end - # Map +json_class+ to a Class object. We use a +case+ instead of a Hash # assigned to a constant because otherwise this file could not be loaded # until all the constants were defined, which means you'd have to load # the world to get json, which would make knife very slow. def class_for_json_class(json_class) case json_class - when CHEF_APICLIENT - Chef::ApiClient - when CHEF_CHECKSUM - Chef::Checksum - when CHEF_COOKBOOKVERSION - Chef::CookbookVersion - when CHEF_DATABAG - Chef::DataBag - when CHEF_DATABAGITEM - Chef::DataBagItem - when CHEF_ENVIRONMENT - Chef::Environment - when CHEF_NODE - Chef::Node - when CHEF_ROLE - Chef::Role - when CHEF_SANDBOX - # a falsey return here will disable object inflation/"create - # additions" in the caller. In Chef 11 this is correct, we just have - # a dummy Chef::Sandbox class for compat with Chef 10 servers. - false - when CHEF_RESOURCE - Chef::Resource - when CHEF_RESOURCECOLLECTION - Chef::ResourceCollection - when /^Chef::Resource/ - Chef::Resource.find_subclass_by_name(json_class) - else - raise JSON::ParserError, "Unsupported `json_class` type '#{json_class}'" + when CHEF_APICLIENT + Chef::ApiClient + when CHEF_CHECKSUM + Chef::Checksum + when CHEF_COOKBOOKVERSION + Chef::CookbookVersion + when CHEF_DATABAG + Chef::DataBag + when CHEF_DATABAGITEM + Chef::DataBagItem + when CHEF_ENVIRONMENT + Chef::Environment + when CHEF_NODE + Chef::Node + when CHEF_ROLE + Chef::Role + when CHEF_SANDBOX + # a falsey return here will disable object inflation/"create + # additions" in the caller. In Chef 11 this is correct, we just have + # a dummy Chef::Sandbox class for compat with Chef 10 servers. + false + when CHEF_RESOURCE + Chef::Resource + when CHEF_RESOURCECOLLECTION + Chef::ResourceCollection + when /^Chef::Resource/ + Chef::Resource.find_subclass_by_name(json_class) + else + raise Chef::Exceptions::JSON::ParseError, "Unsupported `json_class` type '#{json_class}'" end end end end end + diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb index 46cacbd3e0..d3d45bad4b 100644 --- a/lib/chef/knife/bootstrap.rb +++ b/lib/chef/knife/bootstrap.rb @@ -126,7 +126,7 @@ class Chef :short => "-j JSON_ATTRIBS", :long => "--json-attributes", :description => "A JSON string to be added to the first run of chef-client", - :proc => lambda { |o| JSON.parse(o) }, + :proc => lambda { |o| Chef::JSONCompat.parse(o) }, :default => {} option :host_key_verify, @@ -141,7 +141,7 @@ class Chef :proc => Proc.new { |h| Chef::Config[:knife][:hints] ||= Hash.new name, path = h.split("=") - Chef::Config[:knife][:hints][name] = path ? JSON.parse(::File.read(path)) : Hash.new } + Chef::Config[:knife][:hints][name] = path ? Chef::JSONCompat.parse(::File.read(path)) : Hash.new } option :secret, :short => "-s SECRET", diff --git a/lib/chef/knife/bootstrap/archlinux-gems.erb b/lib/chef/knife/bootstrap/archlinux-gems.erb index ab2aa7a7f1..35133e23e5 100644 --- a/lib/chef/knife/bootstrap/archlinux-gems.erb +++ b/lib/chef/knife/bootstrap/archlinux-gems.erb @@ -29,7 +29,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 -%> @@ -56,7 +56,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/centos5-gems.erb b/lib/chef/knife/bootstrap/centos5-gems.erb index 6aacc47179..30418eca2d 100644 --- a/lib/chef/knife/bootstrap/centos5-gems.erb +++ b/lib/chef/knife/bootstrap/centos5-gems.erb @@ -46,7 +46,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 -%> @@ -56,7 +56,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-aix.erb b/lib/chef/knife/bootstrap/chef-aix.erb index 59993b478a..3985415d73 100644 --- a/lib/chef/knife/bootstrap/chef-aix.erb +++ b/lib/chef/knife/bootstrap/chef-aix.erb @@ -42,7 +42,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 -%> @@ -52,7 +52,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 c953a7e433..cffe7007e9 100644 --- a/lib/chef/knife/bootstrap/chef-full.erb +++ b/lib/chef/knife/bootstrap/chef-full.erb @@ -57,7 +57,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 -%> @@ -67,7 +67,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/bootstrap/fedora13-gems.erb b/lib/chef/knife/bootstrap/fedora13-gems.erb index 0aabc31085..431d2bdb00 100644 --- a/lib/chef/knife/bootstrap/fedora13-gems.erb +++ b/lib/chef/knife/bootstrap/fedora13-gems.erb @@ -28,7 +28,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 -%> @@ -38,7 +38,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/ubuntu10.04-apt.erb b/lib/chef/knife/bootstrap/ubuntu10.04-apt.erb index 4549b94d2b..d95de60149 100644 --- a/lib/chef/knife/bootstrap/ubuntu10.04-apt.erb +++ b/lib/chef/knife/bootstrap/ubuntu10.04-apt.erb @@ -28,7 +28,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 -%> @@ -47,7 +47,7 @@ echo 'https_proxy "knife_config[:bootstrap_proxy]"' >> /etc/chef/client.rb <% end -%> 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/ubuntu10.04-gems.erb b/lib/chef/knife/bootstrap/ubuntu10.04-gems.erb index 62ff7c857e..8e41c8f059 100644 --- a/lib/chef/knife/bootstrap/ubuntu10.04-gems.erb +++ b/lib/chef/knife/bootstrap/ubuntu10.04-gems.erb @@ -32,7 +32,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 -%> @@ -42,7 +42,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/ubuntu12.04-gems.erb b/lib/chef/knife/bootstrap/ubuntu12.04-gems.erb index 8e9c6583d0..b6a5aee7e7 100644 --- a/lib/chef/knife/bootstrap/ubuntu12.04-gems.erb +++ b/lib/chef/knife/bootstrap/ubuntu12.04-gems.erb @@ -30,7 +30,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 -%> @@ -40,7 +40,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/cookbook_site_download.rb b/lib/chef/knife/cookbook_site_download.rb index 645b1728e6..de6e21d0d2 100644 --- a/lib/chef/knife/cookbook_site_download.rb +++ b/lib/chef/knife/cookbook_site_download.rb @@ -58,7 +58,7 @@ class Chef private def cookbooks_api_url - 'http://cookbooks.opscode.com/api/v1/cookbooks' + 'https://supermarket.getchef.com/api/v1/cookbooks' end def current_cookbook_data diff --git a/lib/chef/knife/cookbook_site_list.rb b/lib/chef/knife/cookbook_site_list.rb index fe83b71388..6fcf7e6064 100644 --- a/lib/chef/knife/cookbook_site_list.rb +++ b/lib/chef/knife/cookbook_site_list.rb @@ -41,7 +41,7 @@ class Chef end def get_cookbook_list(items=10, start=0, cookbook_collection={}) - cookbooks_url = "http://cookbooks.opscode.com/api/v1/cookbooks?items=#{items}&start=#{start}" + cookbooks_url = "https://supermarket.getchef.com/api/v1/cookbooks?items=#{items}&start=#{start}" cr = noauth_rest.get_rest(cookbooks_url) cr["items"].each do |cookbook| cookbook_collection[cookbook["cookbook_name"]] = cookbook diff --git a/lib/chef/knife/cookbook_site_search.rb b/lib/chef/knife/cookbook_site_search.rb index b636276cba..ec4d196ee3 100644 --- a/lib/chef/knife/cookbook_site_search.rb +++ b/lib/chef/knife/cookbook_site_search.rb @@ -29,7 +29,7 @@ class Chef end def search_cookbook(query, items=10, start=0, cookbook_collection={}) - cookbooks_url = "http://cookbooks.opscode.com/api/v1/search?q=#{query}&items=#{items}&start=#{start}" + cookbooks_url = "https://supermarket.getchef.com/api/v1/search?q=#{query}&items=#{items}&start=#{start}" cr = noauth_rest.get_rest(cookbooks_url) cr["items"].each do |cookbook| cookbook_collection[cookbook["cookbook_name"]] = cookbook diff --git a/lib/chef/knife/cookbook_site_share.rb b/lib/chef/knife/cookbook_site_share.rb index 4dcce42d7f..eaa30ea743 100644 --- a/lib/chef/knife/cookbook_site_share.rb +++ b/lib/chef/knife/cookbook_site_share.rb @@ -85,9 +85,9 @@ class Chef end def do_upload(cookbook_filename, cookbook_category, user_id, user_secret_filename) - uri = "http://cookbooks.opscode.com/api/v1/cookbooks" + uri = "https://supermarket.getchef.com/api/v1/cookbooks" - category_string = { 'category'=>cookbook_category }.to_json + category_string = Chef::JSONCompat.to_json({ 'category'=>cookbook_category }) http_resp = Chef::CookbookSiteStreamingUploader.post(uri, user_id, user_secret_filename, { :tarball => File.open(cookbook_filename), diff --git a/lib/chef/knife/cookbook_site_show.rb b/lib/chef/knife/cookbook_site_show.rb index d15098e915..c520c00621 100644 --- a/lib/chef/knife/cookbook_site_show.rb +++ b/lib/chef/knife/cookbook_site_show.rb @@ -31,14 +31,14 @@ class Chef def get_cookbook_data case @name_args.length when 1 - noauth_rest.get_rest("http://cookbooks.opscode.com/api/v1/cookbooks/#{@name_args[0]}") + noauth_rest.get_rest("https://supermarket.getchef.com/api/v1/cookbooks/#{@name_args[0]}") when 2 - noauth_rest.get_rest("http://cookbooks.opscode.com/api/v1/cookbooks/#{@name_args[0]}/versions/#{name_args[1].gsub('.', '_')}") + noauth_rest.get_rest("https://supermarket.getchef.com/api/v1/cookbooks/#{@name_args[0]}/versions/#{name_args[1].gsub('.', '_')}") end end def get_cookbook_list(items=10, start=0, cookbook_collection={}) - cookbooks_url = "http://cookbooks.opscode.com/api/v1/cookbooks?items=#{items}&start=#{start}" + cookbooks_url = "https://supermarket.getchef.com/api/v1/cookbooks?items=#{items}&start=#{start}" cr = noauth_rest.get_rest(cookbooks_url) cr["items"].each do |cookbook| cookbook_collection[cookbook["cookbook_name"]] = cookbook diff --git a/lib/chef/knife/cookbook_site_unshare.rb b/lib/chef/knife/cookbook_site_unshare.rb index a2828549a0..f095885f15 100644 --- a/lib/chef/knife/cookbook_site_unshare.rb +++ b/lib/chef/knife/cookbook_site_unshare.rb @@ -41,7 +41,7 @@ class Chef confirm "Do you really want to unshare the cookbook #{@cookbook_name}" begin - rest.delete_rest "http://cookbooks.opscode.com/api/v1/cookbooks/#{@name_args[0]}" + rest.delete_rest "https://supermarket.getchef.com/api/v1/cookbooks/#{@name_args[0]}" rescue Net::HTTPServerException => e raise e unless e.message =~ /Forbidden/ ui.error "Forbidden: You must be the maintainer of #{@cookbook_name} to unshare it." diff --git a/lib/chef/knife/deps.rb b/lib/chef/knife/deps.rb index b2a39a0725..89fd42124f 100644 --- a/lib/chef/knife/deps.rb +++ b/lib/chef/knife/deps.rb @@ -9,6 +9,7 @@ class Chef deps do require 'chef/chef_fs/file_system' + require 'chef/json_compat' require 'chef/run_list' end @@ -77,7 +78,7 @@ class Chef return entry.chef_object.metadata.dependencies.keys.map { |cookbook| "/cookbooks/#{cookbook}" } elsif entry.parent && entry.parent.path == '/nodes' - node = JSON.parse(entry.read, :create_additions => false) + node = Chef::JSONCompat.parse(entry.read, :create_additions => false) result = [] if node['chef_environment'] && node['chef_environment'] != '_default' result << "/environments/#{node['chef_environment']}.json" @@ -88,7 +89,7 @@ class Chef result elsif entry.parent && entry.parent.path == '/roles' - role = JSON.parse(entry.read, :create_additions => false) + role = Chef::JSONCompat.parse(entry.read, :create_additions => false) result = [] if role['run_list'] dependencies_from_runlist(role['run_list']).each do |dependency| diff --git a/lib/chef/node.rb b/lib/chef/node.rb index 17ec1d0f0a..261c4a3f10 100644 --- a/lib/chef/node.rb +++ b/lib/chef/node.rb @@ -416,7 +416,7 @@ class Chef # Serialize this object as a hash def to_json(*a) - for_json.to_json(*a) + Chef::JSONCompat.to_json(for_json, *a) end def for_json diff --git a/lib/chef/provider/deploy/revision.rb b/lib/chef/provider/deploy/revision.rb index f1eb171cd7..89710088d1 100644 --- a/lib/chef/provider/deploy/revision.rb +++ b/lib/chef/provider/deploy/revision.rb @@ -97,7 +97,7 @@ class Chef end def save_cache(cache) - Chef::FileCache.store("revision-deploys/#{new_resource.name}", cache.to_json) + Chef::FileCache.store("revision-deploys/#{new_resource.name}", Chef::JSONCompat.to_json(cache)) cache end diff --git a/lib/chef/provider/dsc_script.rb b/lib/chef/provider/dsc_script.rb index 5d7322842c..b88cb78fbe 100644 --- a/lib/chef/provider/dsc_script.rb +++ b/lib/chef/provider/dsc_script.rb @@ -46,9 +46,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 @@ -56,8 +58,26 @@ class Chef true end + def define_resource_requirements + requirements.assert(:run) do |a| + err = [ + 'Could not find PowerShell DSC support 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) @@ -77,9 +97,8 @@ class Chef end def get_augmented_configuration_flags(configuration_data_path) - updated_flags = nil + updated_flags = @dsc_resource.flags.nil? ? {} : @dsc_resource.flags.dup if configuration_data_path - updated_flags = @dsc_resource.flags.nil? ? {} : @dsc_resource.flags.dup Chef::Util::PathHelper.validate_path(configuration_data_path) updated_flags[:configurationdata] = configuration_data_path end @@ -143,6 +162,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/env.rb b/lib/chef/provider/env.rb index e857d74d68..600cac1e6b 100644 --- a/lib/chef/provider/env.rb +++ b/lib/chef/provider/env.rb @@ -58,20 +58,22 @@ class Chef # ==== Returns # <true>:: If a change is required # <false>:: If a change is not required - def compare_value + def requires_modify_or_create? if @new_resource.delim #e.g. check for existing value within PATH - not @current_resource.value.split(@new_resource.delim).any? do |val| - val == @new_resource.value + not new_values.all? do |val| + current_values.include? val end else @new_resource.value != @current_resource.value end end + alias_method :compare_value, :requires_modify_or_create? + def action_create if @key_exists - if compare_value + if requires_modify_or_create? modify_env Chef::Log.info("#{@new_resource} altered") @new_resource.updated_by_last_action(true) @@ -91,13 +93,14 @@ class Chef # after we removed the element. def delete_element return false unless @new_resource.delim #no delim: delete the key - if compare_value + needs_delete = new_values.any? { |v| current_values.include?(v) } + if !needs_delete Chef::Log.debug("#{@new_resource} element '#{@new_resource.value}' does not exist") return true #do not delete the key else new_value = - @current_resource.value.split(@new_resource.delim).select { |item| - item != @new_resource.value + current_values.select { |item| + not new_values.include?(item) }.join(@new_resource.delim) if new_value.empty? @@ -122,7 +125,7 @@ class Chef def action_modify if @key_exists - if compare_value + if requires_modify_or_create? modify_env Chef::Log.info("#{@new_resource} modified") @new_resource.updated_by_last_action(true) @@ -142,11 +145,23 @@ class Chef def modify_env if @new_resource.delim - #e.g. add to PATH - @new_resource.value << @new_resource.delim << @current_resource.value + values = new_values.reject do |v| + current_values.include?(v) + end + @new_resource.value((values + [@current_resource.value]).join(@new_resource.delim)) end create_env end + + # Returns the current values to split by delimiter + def current_values + @current_values ||= @current_resource.value.split(@new_resource.delim) + end + + # Returns the new values to split by delimiter + def new_values + @new_values ||= @new_resource.value.split(@new_resource.delim) + end end end end diff --git a/lib/chef/provider/remote_file/cache_control_data.rb b/lib/chef/provider/remote_file/cache_control_data.rb index 0add74f50a..95b2bc6a1b 100644 --- a/lib/chef/provider/remote_file/cache_control_data.rb +++ b/lib/chef/provider/remote_file/cache_control_data.rb @@ -140,7 +140,7 @@ class Chef def load_data Chef::JSONCompat.from_json(load_json_data) - rescue Chef::Exceptions::FileNotFound, FFI_Yajl::ParseError, JSON::ParserError + rescue Chef::Exceptions::FileNotFound, FFI_Yajl::ParseError, Chef::Exceptions::JSON::ParseError false end diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index f6283e4033..a112e3b463 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -544,7 +544,7 @@ F # Serialize this object as a hash def to_json(*a) results = as_json - results.to_json(*a) + Chef::JSONCompat.to_json(results, *a) end def to_hash 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_collection.rb b/lib/chef/resource_collection.rb index a528a18aed..3097707a13 100644 --- a/lib/chef/resource_collection.rb +++ b/lib/chef/resource_collection.rb @@ -198,7 +198,7 @@ class Chef 'json_class' => self.class.name, 'instance_vars' => instance_vars } - results.to_json(*a) + Chef::JSONCompat.to_json(results, *a) end def self.json_create(o) diff --git a/lib/chef/resource_reporter.rb b/lib/chef/resource_reporter.rb index 046e4e82c6..a19f26125e 100644 --- a/lib/chef/resource_reporter.rb +++ b/lib/chef/resource_reporter.rb @@ -230,7 +230,7 @@ class Chef resource_history_url = "reports/nodes/#{node_name}/runs/#{run_id}" Chef::Log.info("Sending resource update report (run-id: #{run_id})") Chef::Log.debug run_data.inspect - compressed_data = encode_gzip(run_data.to_json) + compressed_data = encode_gzip(Chef::JSONCompat.to_json(run_data)) Chef::Log.debug("Sending compressed run data...") # Since we're posting compressed data we can not directly call post_rest which expects JSON reporting_url = @rest_client.create_url(resource_history_url) @@ -273,7 +273,7 @@ class Chef resource_record.for_json end run_data["status"] = @status - run_data["run_list"] = @run_status.node.run_list.to_json + run_data["run_list"] = Chef::JSONCompat.to_json(@run_status.node.run_list) run_data["total_res_count"] = @total_res_count.to_s run_data["data"] = {} run_data["start_time"] = start_time.to_s @@ -283,7 +283,7 @@ class Chef exception_data = {} exception_data["class"] = exception.inspect exception_data["message"] = exception.message - exception_data["backtrace"] = exception.backtrace.to_json + exception_data["backtrace"] = Chef::JSONCompat.to_json(exception.backtrace) exception_data["description"] = @error_descriptions run_data["data"]["exception"] = exception_data end diff --git a/lib/chef/role.rb b/lib/chef/role.rb index 57f3a2aa29..aeea873051 100644 --- a/lib/chef/role.rb +++ b/lib/chef/role.rb @@ -143,7 +143,7 @@ class Chef # Serialize this object as a hash def to_json(*a) - to_hash.to_json(*a) + Chef::JSONCompat.to_json(to_hash, *a) end def update_from!(o) diff --git a/lib/chef/run_list.rb b/lib/chef/run_list.rb index 684c5e19fc..d0ba342e2c 100644 --- a/lib/chef/run_list.rb +++ b/lib/chef/run_list.rb @@ -86,7 +86,7 @@ class Chef end def to_json(*args) - to_a.map { |item| item.to_s}.to_json(*args) + Chef::JSONCompat.to_json(to_a.map { |item| item.to_s}, *args) end def empty? diff --git a/lib/chef/user.rb b/lib/chef/user.rb index e2ef45dc5c..6569a97f00 100644 --- a/lib/chef/user.rb +++ b/lib/chef/user.rb @@ -73,7 +73,7 @@ class Chef end def to_json(*a) - to_hash.to_json(*a) + Chef::JSONCompat.to_json(to_hash, *a) end def destroy diff --git a/lib/chef/util/dsc/local_configuration_manager.rb b/lib/chef/util/dsc/local_configuration_manager.rb index 4a56b6a397..f498a2bfea 100644 --- a/lib/chef/util/dsc/local_configuration_manager.rb +++ b/lib/chef/util/dsc/local_configuration_manager.rb @@ -29,7 +29,7 @@ class Chef::Util::DSC def test_configuration(configuration_document) status = run_configuration_cmdlet(configuration_document) - handle_what_if_exception!(status.stderr) unless status.succeeded? + log_what_if_exception(status.stderr) unless status.succeeded? configuration_update_required?(status.return_value) end @@ -78,18 +78,22 @@ $ProgressPreference = 'SilentlyContinue';start-dscconfiguration -path #{@configu EOH end - def handle_what_if_exception!(what_if_exception_output) - if what_if_exception_output.gsub(/\s+/, ' ') =~ /A parameter cannot be found that matches parameter name 'Whatif'/i - # LCM returns an error if any of the resources do not support the opptional What-If - Chef::Log::warn("Received error while testing configuration due to resource not supporting 'WhatIf'") - elsif output_has_dsc_module_failure?(what_if_exception_output) - Chef::Log::warn("Received error while testing configuration due to a module for an imported resource possibly not being fully installed:\n#{what_if_exception_output.gsub(/\s+/, ' ')}") - else - raise Chef::Exceptions::PowershellCmdletException, "Powershell Cmdlet failed: #{what_if_exception_output.gsub(/\s+/, ' ')}" - end + def log_what_if_exception(what_if_exception_output) + if whatif_not_supported?(what_if_exception_output) + # LCM returns an error if any of the resources do not support the opptional What-If + Chef::Log::warn("Received error while testing configuration due to resource not supporting 'WhatIf'") + elsif dsc_module_import_failure?(what_if_exception_output) + Chef::Log::warn("Received error while testing configuration due to a module for an imported resource possibly not being fully installed:\n#{what_if_exception_output.gsub(/\s+/, ' ')}") + else + Chef::Log::warn("Received error while testing configuration:\n#{what_if_exception_output.gsub(/\s+/, ' ')}") + end + end + + def whatif_not_supported?(what_if_exception_output) + !! (what_if_exception_output.gsub(/[\r\n]+/, '').gsub(/\s+/, ' ') =~ /A parameter cannot be found that matches parameter name 'Whatif'/i) end - def output_has_dsc_module_failure?(what_if_output) + def dsc_module_import_failure?(what_if_output) !! (what_if_output =~ /\sCimException/ && what_if_output =~ /ProviderOperationExecutionFailure/ && what_if_output =~ /\smodule\s+is\s+installed/) 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/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/resource/dsc_script_spec.rb b/spec/functional/resource/dsc_script_spec.rb index fa13296c02..a736949c6b 100644 --- a/spec/functional/resource/dsc_script_spec.rb +++ b/spec/functional/resource/dsc_script_spec.rb @@ -81,17 +81,28 @@ describe Chef::Resource::DscScript, :windows_powershell_dsc_only do let(:test_registry_value) { 'Registration' } let(:test_registry_data1) { 'LL927' } let(:test_registry_data2) { 'LL928' } - let(:dsc_code) { <<-EOH + let(:reg_key_name_param_name) { 'testregkeyname' } + let(:reg_key_value_param_name) { 'testregvaluename' } + let(:registry_embedded_parameters) { "$#{reg_key_name_param_name} = '#{test_registry_key}';$#{reg_key_value_param_name} = '#{test_registry_value}'"} + let(:dsc_reg_code) { <<-EOH + #{registry_embedded_parameters} Registry "ChefRegKey" { - Key = '#{test_registry_key}' - ValueName = '#{test_registry_value}' + Key = $#{reg_key_name_param_name} + ValueName = $#{reg_key_value_param_name} ValueData = '#{test_registry_data}' Ensure = 'Present' } EOH } + let(:dsc_code) { dsc_reg_code } + let(:dsc_reg_script) { <<-EOH + param($testregkeyname, $testregvaluename) + #{dsc_reg_code} +EOH + } + let(:dsc_user_prefix) { 'dsc' } let(:dsc_user_suffix) { 'chefx' } let(:dsc_user) {"#{dsc_user_prefix}_usr_#{dsc_user_suffix}" } @@ -175,7 +186,7 @@ environment "whatsmydir" Ensure = 'Present' } EOH - } +} let(:dsc_config_name) { dsc_test_resource_base.name @@ -227,41 +238,79 @@ environment 'removethis' EOH removal_resource.run_action(:run) end - let(:dsc_code) { dsc_environment_config } - it 'should not raise an exception if the cwd is not etc' do - dsc_test_resource.cwd(dsc_environment_no_fail_not_etc_directory) - expect {dsc_test_resource.run_action(:run)}.not_to raise_error - end - it 'should raise an exception if the cwd is etc' do - dsc_test_resource.cwd(dsc_environment_fail_etc_directory) - expect {dsc_test_resource.run_action(:run)}.to raise_error(Chef::Exceptions::PowershellCmdletException) - begin - dsc_test_resource.run_action(:run) - rescue Chef::Exceptions::PowershellCmdletException => e - expect(e.message).to match(exception_message_signature) + describe 'when the DSC configuration contains code that raises an exception if cwd has a specific value' do + let(:dsc_code) { dsc_environment_config } + it 'should not raise an exception if the cwd is not etc' do + dsc_test_resource.cwd(dsc_environment_no_fail_not_etc_directory) + expect {dsc_test_resource.run_action(:run)}.not_to raise_error + end + + it 'should raise an exception if the cwd is etc' do + dsc_test_resource.cwd(dsc_environment_fail_etc_directory) + expect {dsc_test_resource.run_action(:run)}.to raise_error(Chef::Exceptions::PowershellCmdletException) + begin + dsc_test_resource.run_action(:run) + rescue Chef::Exceptions::PowershellCmdletException => e + expect(e.message).to match(exception_message_signature) + end end end end shared_examples_for 'a parameterized DSC configuration script' do - context 'when specifying environment variables in the environment attribute' do - let(:dsc_user_prefix_code) { dsc_user_prefix_env_code } - let(:dsc_user_suffix_code) { dsc_user_suffix_env_code } - it_behaves_like 'a dsc_script with configuration that uses environment variables' + let(:dsc_user_prefix_code) { dsc_user_prefix_env_code } + let(:dsc_user_suffix_code) { dsc_user_suffix_env_code } + it_behaves_like 'a dsc_script with configuration that uses environment variables' + end + + shared_examples_for 'a dsc_script without configuration data that takes parameters' do + context 'when configuration data is not specified' do + + before(:each) do + test_key_resource = Chef::Resource::RegistryKey.new(test_registry_key, dsc_test_run_context) + test_key_resource.recursive(true) + test_key_resource.run_action(:delete_key) + end + + after(:each) do + test_key_resource = Chef::Resource::RegistryKey.new(test_registry_key, dsc_test_run_context) + test_key_resource.recursive(true) + test_key_resource.run_action(:delete_key) + end + + let(:test_registry_data) { test_registry_data1 } + let(:dsc_parameterized_env_param_value) { "val" + Random::rand.to_s } + + it 'should have a default value of nil for the configuration_data attribute' do + expect(dsc_test_resource.configuration_data).to eql(nil) + end + + it 'should have a default value of nil for the configuration_data_path attribute' do + expect(dsc_test_resource.configuration_data_script).to eql(nil) + end + + let(:dsc_test_resource) { dsc_resource_from_path } + let(:registry_embedded_parameters) { '' } + let(:dsc_code) { dsc_reg_script } + + it 'should set a registry key according to parameters passed to the configuration' do + dsc_test_resource.configuration_name(config_name_value) + dsc_test_resource.flags({:"#{reg_key_name_param_name}" => test_registry_key, :"#{reg_key_value_param_name}" => test_registry_value}) + expect(dsc_test_resource.registry_key_exists?(test_registry_key)).to eq(false) + dsc_test_resource.run_action(:run) + expect(dsc_test_resource.registry_key_exists?(test_registry_key)).to eq(true) + expect(dsc_test_resource.registry_value_exists?(test_registry_key, {:name => test_registry_value, :type => :string, :data => test_registry_data})).to eq(true) + end end end shared_examples_for 'a dsc_script with configuration data' do - context 'when using the configuration_data attribute' do - let(:configuration_data_attribute) { 'configuration_data' } - it_behaves_like 'a dsc_script with configuration data set via an attribute' - end + let(:configuration_data_attribute) { 'configuration_data' } + it_behaves_like 'a dsc_script with configuration data set via an attribute' - context 'when using the configuration_data_script attribute' do - let(:configuration_data_attribute) { 'configuration_data_script' } - it_behaves_like 'a dsc_script with configuration data set via an attribute' - end + let(:configuration_data_attribute) { 'configuration_data_script' } + it_behaves_like 'a dsc_script with configuration data set via an attribute' end shared_examples_for 'a dsc_script with configuration data set via an attribute' do @@ -282,33 +331,28 @@ EOH end shared_examples_for 'a dsc_script with configuration data that takes parameters' do - context 'when script code takes parameters for configuration' do - let(:dsc_user_code) { dsc_user_param_code } - let(:config_param_section) { config_params } - let(:config_flags) {{:"#{dsc_user_prefix_param_name}" => "#{dsc_user_prefix}", :"#{dsc_user_suffix_param_name}" => "#{dsc_user_suffix}"}} - it 'does not directly contain the user name' do - configuration_script_content = ::File.open(dsc_test_resource.command) do | file | - file.read - end - expect(configuration_script_content.include?(dsc_user)).to be(false) + let(:dsc_user_code) { dsc_user_param_code } + let(:config_param_section) { config_params } + let(:config_flags) {{:"#{dsc_user_prefix_param_name}" => "#{dsc_user_prefix}", :"#{dsc_user_suffix_param_name}" => "#{dsc_user_suffix}"}} + it 'does not directly contain the user name' do + configuration_script_content = ::File.open(dsc_test_resource.command) do | file | + file.read end - it_behaves_like 'a dsc_script with configuration data' + expect(configuration_script_content.include?(dsc_user)).to be(false) end - + it_behaves_like 'a dsc_script with configuration data' end shared_examples_for 'a dsc_script with configuration data that uses environment variables' do - context 'when script code uses environment variables' do - let(:dsc_user_code) { dsc_user_env_code } + let(:dsc_user_code) { dsc_user_env_code } - it 'does not directly contain the user name' do - configuration_script_content = ::File.open(dsc_test_resource.command) do | file | - file.read - end - expect(configuration_script_content.include?(dsc_user)).to be(false) + it 'does not directly contain the user name' do + configuration_script_content = ::File.open(dsc_test_resource.command) do | file | + file.read end - it_behaves_like 'a dsc_script with configuration data' + expect(configuration_script_content.include?(dsc_user)).to be(false) end + it_behaves_like 'a dsc_script with configuration data' end context 'when supplying configuration through the configuration attribute' do @@ -333,5 +377,6 @@ EOH it_behaves_like 'a dsc_script with configuration data' it_behaves_like 'a dsc_script with configuration data that uses environment variables' it_behaves_like 'a dsc_script with configuration data that takes parameters' + it_behaves_like 'a dsc_script without configuration data that takes parameters' end end diff --git a/spec/functional/resource/env_spec.rb b/spec/functional/resource/env_spec.rb index 24fe5e1dff..cf77fef703 100755 --- a/spec/functional/resource/env_spec.rb +++ b/spec/functional/resource/env_spec.rb @@ -126,7 +126,8 @@ describe Chef::Resource::Env, :windows_only do context 'when using PATH' do let(:random_name) { Time.now.to_i } let(:env_val) { "#{env_value_expandable}_#{random_name}"} - let(:path_before) { test_resource.provider_for_action(test_resource.action).env_value('PATH') } + let!(:path_before) { test_resource.provider_for_action(test_resource.action).env_value('PATH') || '' } + let!(:env_path_before) { ENV['PATH'] } it 'should expand PATH' do path_before.should_not include(env_val) @@ -142,9 +143,7 @@ describe Chef::Resource::Env, :windows_only do test_resource.key_name('PATH') test_resource.value(path_before) test_resource.run_action(:create) - if test_resource.provider_for_action(test_resource.action).env_value('PATH') != path_before - raise 'Failed to cleanup after ourselves' - end + ENV['PATH'] = env_path_before end end diff --git a/spec/functional/util/powershell/cmdlet_spec.rb b/spec/functional/util/powershell/cmdlet_spec.rb index 63d1ac09b5..ca142e66b6 100644 --- a/spec/functional/util/powershell/cmdlet_spec.rb +++ b/spec/functional/util/powershell/cmdlet_spec.rb @@ -16,7 +16,6 @@ # limitations under the License. # -require 'json' require File.expand_path('../../../../spec_helper', __FILE__) describe Chef::Util::Powershell::Cmdlet, :windows_only do @@ -91,7 +90,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 e8a3b3dde5..e50d39317d 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 extend IntegrationSupport include KnifeSupport diff --git a/spec/integration/knife/chef_repo_path_spec.rb b/spec/integration/knife/chef_repo_path_spec.rb index 72d2ffbf75..7d2e27a260 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 extend IntegrationSupport include KnifeSupport @@ -287,6 +287,7 @@ EOM knife('show --local /clients/blah.json').should_succeed <<EOM /clients/blah.json: { + } EOM end @@ -374,6 +375,7 @@ EOM knife('show --local /environments/blah.json').should_succeed <<EOM /environments/blah.json: { + } EOM end @@ -386,6 +388,7 @@ EOM knife('show --local /nodes/blah.json').should_succeed <<EOM /nodes/blah.json: { + } EOM end @@ -398,6 +401,7 @@ EOM knife('show --local /roles/blah.json').should_succeed <<EOM /roles/blah.json: { + } EOM end @@ -410,6 +414,7 @@ EOM knife('show --local /users/blah.json').should_succeed <<EOM /users/blah.json: { + } EOM end diff --git a/spec/integration/knife/chef_repository_file_system_spec.rb b/spec/integration/knife/chef_repository_file_system_spec.rb index 68ca5f89f4..37f458ed68 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 extend IntegrationSupport include KnifeSupport diff --git a/spec/integration/knife/chefignore_spec.rb b/spec/integration/knife/chefignore_spec.rb index 4c3d2fa3aa..05a9e83447 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 extend IntegrationSupport include KnifeSupport diff --git a/spec/integration/knife/common_options_spec.rb b/spec/integration/knife/common_options_spec.rb index e6b90613e5..9abee507af 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 extend IntegrationSupport include KnifeSupport diff --git a/spec/integration/knife/cookbook_api_ipv6_spec.rb b/spec/integration/knife/cookbook_api_ipv6_spec.rb index ad7d5e857f..596913b0ef 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 extend IntegrationSupport include Chef::Mixin::ShellOut diff --git a/spec/integration/knife/delete_spec.rb b/spec/integration/knife/delete_spec.rb index 8d9b972ca4..88fb2d7aee 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 extend IntegrationSupport include KnifeSupport diff --git a/spec/integration/knife/deps_spec.rb b/spec/integration/knife/deps_spec.rb index 7f434f844d..d8a4170175 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 extend IntegrationSupport include KnifeSupport diff --git a/spec/integration/knife/diff_spec.rb b/spec/integration/knife/diff_spec.rb index 2e36f39c82..debd96dc43 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 extend IntegrationSupport include KnifeSupport @@ -275,7 +275,7 @@ EOM when_the_repository 'has an environment with bad JSON' do file 'environments/x.json', '{' it 'knife diff reports an error and does a textual diff' do - knife('diff /environments/x.json').should_succeed(/- "name": "x"/, :stderr => "WARN: Parse error reading #{path_to('environments/x.json')} as JSON: A JSON text must at least contain two octets!\n") + knife('diff /environments/x.json').should_succeed(/- "name": "x"/, :stderr => /WARN: Parse error reading #{path_to('environments/x.json')} as JSON: parse error: premature EOF\n/) end end end @@ -528,7 +528,7 @@ EOM when_the_repository 'has an environment with bad JSON' do file 'environments/x.json', '{' it 'knife diff reports an error and does a textual diff' do - knife('diff /environments/x.json').should_succeed(/- "name": "x"/, :stderr => "WARN: Parse error reading #{path_to('environments/x.json')} as JSON: A JSON text must at least contain two octets!\n") + knife('diff /environments/x.json').should_succeed(/- "name": "x"/, :stderr => /WARN: Parse error reading #{path_to('environments/x.json')} as JSON: parse error: premature EOF\n/) end end end diff --git a/spec/integration/knife/download_spec.rb b/spec/integration/knife/download_spec.rb index f266b47b1b..484a93253b 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 extend IntegrationSupport include KnifeSupport @@ -496,7 +496,7 @@ EOM when_the_repository 'has an environment with bad JSON' do file 'environments/x.json', '{' it 'knife download succeeds' do - knife('download /environments/x.json').should_succeed "Updated /environments/x.json\n", :stderr => "WARN: Parse error reading #{path_to('environments/x.json')} as JSON: A JSON text must at least contain two octets!\n" + knife('download /environments/x.json').should_succeed "Updated /environments/x.json\n", :stderr => /WARN: Parse error reading #{path_to('environments/x.json')} as JSON: parse error: premature EOF\n/ knife('diff --name-status /environments/x.json').should_succeed '' end end @@ -946,7 +946,7 @@ EOM when_the_repository 'has an environment with bad JSON' do file 'environments/x.json', '{' it 'knife download succeeds' do - knife('download /environments/x.json').should_succeed "Updated /environments/x.json\n", :stderr => "WARN: Parse error reading #{path_to('environments/x.json')} as JSON: A JSON text must at least contain two octets!\n" + knife('download /environments/x.json').should_succeed "Updated /environments/x.json\n", :stderr => /WARN: Parse error reading #{path_to('environments/x.json')} as JSON: parse error: premature EOF\n/ knife('diff --name-status /environments/x.json').should_succeed '' end end diff --git a/spec/integration/knife/list_spec.rb b/spec/integration/knife/list_spec.rb index b9e72c5573..a39060d5aa 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 extend IntegrationSupport include KnifeSupport diff --git a/spec/integration/knife/raw_spec.rb b/spec/integration/knife/raw_spec.rb index 2a9b5d8904..c66073a7ce 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 extend IntegrationSupport include KnifeSupport include AppServerSupport @@ -44,12 +44,16 @@ describe 'knife raw' do "chef_type": "node", "chef_environment": "_default", "override": { + }, "normal": { + }, "default": { + }, "automatic": { + }, "run_list": [ @@ -70,13 +74,16 @@ EOM "json_class": "Chef::Role", "chef_type": "role", "default_attributes": { + }, "override_attributes": { + }, "run_list": [ ], "env_run_lists": { + } } EOM @@ -111,13 +118,16 @@ EOM "json_class": "Chef::Role", "chef_type": "role", "default_attributes": { + }, "override_attributes": { + }, "run_list": [ ], "env_run_lists": { + } } EOM diff --git a/spec/integration/knife/redirection_spec.rb b/spec/integration/knife/redirection_spec.rb index ebfd40966e..732634a411 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 extend IntegrationSupport include KnifeSupport include AppServerSupport diff --git a/spec/integration/knife/serve_spec.rb b/spec/integration/knife/serve_spec.rb index 6f8d046518..6d1aef22e5 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 extend IntegrationSupport include KnifeSupport include AppServerSupport diff --git a/spec/integration/knife/show_spec.rb b/spec/integration/knife/show_spec.rb index 5b15110e41..06410e0232 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 extend IntegrationSupport include KnifeSupport diff --git a/spec/integration/knife/upload_spec.rb b/spec/integration/knife/upload_spec.rb index 46b804205f..630653fbcf 100644 --- a/spec/integration/knife/upload_spec.rb +++ b/spec/integration/knife/upload_spec.rb @@ -20,7 +20,7 @@ require 'chef/knife/upload' require 'chef/knife/diff' require 'chef/knife/raw' -describe 'knife upload' do +describe 'knife upload', :workstation do extend IntegrationSupport include KnifeSupport @@ -237,7 +237,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 @@ -256,7 +256,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' @@ -528,8 +528,8 @@ EOM when_the_repository 'has an environment with bad JSON' do file 'environments/x.json', '{' it 'knife upload tries and fails' do - knife('upload /environments/x.json').should_fail "WARN: Parse error reading #{path_to('environments/x.json')} as JSON: A JSON text must at least contain two octets!\nERROR: /environments/x.json failed to write: Parse error reading JSON: A JSON text must at least contain two octets!\n" - knife('diff --name-status /environments/x.json').should_succeed "M\t/environments/x.json\n", :stderr => "WARN: Parse error reading #{path_to('environments/x.json')} as JSON: A JSON text must at least contain two octets!\n" + knife('upload /environments/x.json').should_fail /WARN: Parse error reading #{path_to('environments/x.json')} as JSON: parse error: premature EOF\n.+ERROR: \/environments\/x.json failed to write: Parse error reading JSON: parse error: premature EOF\n/m + knife('diff --name-status /environments/x.json').should_succeed "M\t/environments/x.json\n", :stderr => /WARN: Parse error reading #{path_to('environments/x.json')} as JSON: parse error: premature EOF\n/ end end @@ -554,7 +554,7 @@ EOM when_the_repository 'has an environment with bad JSON' do file 'environments/x.json', '{' it 'knife upload tries and fails' do - knife('upload /environments/x.json').should_fail "ERROR: /environments failed to create_child: Parse error reading JSON creating child 'x.json': A JSON text must at least contain two octets!\n" + knife('upload /environments/x.json').should_fail /ERROR: \/environments failed to create_child: Parse error reading JSON creating child 'x.json': parse error: premature EOF\n/ knife('diff --name-status /environments/x.json').should_succeed "A\t/environments/x.json\n" end end @@ -1007,8 +1007,8 @@ EOM when_the_repository 'has an environment with bad JSON' do file 'environments/x.json', '{' it 'knife upload tries and fails' do - knife('upload /environments/x.json').should_fail "WARN: Parse error reading #{path_to('environments/x.json')} as JSON: A JSON text must at least contain two octets!\nERROR: /environments/x.json failed to write: Parse error reading JSON: A JSON text must at least contain two octets!\n" - knife('diff --name-status /environments/x.json').should_succeed "M\t/environments/x.json\n", :stderr => "WARN: Parse error reading #{path_to('environments/x.json')} as JSON: A JSON text must at least contain two octets!\n" + knife('upload /environments/x.json').should_fail /WARN: Parse error reading #{path_to('environments/x.json')} as JSON: parse error: premature EOF\n.+ERROR: \/environments\/x.json failed to write: Parse error reading JSON: parse error: premature EOF\n/m + knife('diff --name-status /environments/x.json').should_succeed "M\t/environments/x.json\n", :stderr => /WARN: Parse error reading #{path_to('environments/x.json')} as JSON: parse error: premature EOF\n/ end end @@ -1033,7 +1033,7 @@ EOM when_the_repository 'has an environment with bad JSON' do file 'environments/x.json', '{' it 'knife upload tries and fails' do - knife('upload /environments/x.json').should_fail "ERROR: /environments failed to create_child: Parse error reading JSON creating child 'x.json': A JSON text must at least contain two octets!\n" + knife('upload /environments/x.json').should_fail /ERROR: \/environments failed to create_child: Parse error reading JSON creating child 'x.json': parse error: premature EOF\n/ knife('diff --name-status /environments/x.json').should_succeed "A\t/environments/x.json\n" end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e73010d2b5..c43a16cd99 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? @@ -182,3 +185,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 abed4c2715..d262e58b48 100644 --- a/spec/support/shared/integration/integration_helper.rb +++ b/spec/support/shared/integration/integration_helper.rb @@ -21,7 +21,6 @@ require 'tmpdir' require 'fileutils' require 'chef/config' require 'chef_zero/rspec' -require 'json' require 'support/shared/integration/knife_support' require 'support/shared/integration/app_server_support' require 'spec_helper' @@ -69,7 +68,7 @@ module IntegrationSupport File.open(filename, 'w') do |file| raw = case contents when Hash - JSON.pretty_generate(contents) + Chef::JSONCompat.to_json_pretty(contents) when Array contents.join("\n") else diff --git a/spec/support/shared/shared_examples.rb b/spec/support/shared/shared_examples.rb new file mode 100644 index 0000000000..ccdd8cf316 --- /dev/null +++ b/spec/support/shared/shared_examples.rb @@ -0,0 +1,10 @@ +# 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 + + it "should allow consumers to call #to_json or Chef::JSONCompat.to_json" do + expect(subject.to_json).to eq(Chef::JSONCompat.to_json(subject)) + end + +end diff --git a/spec/tiny_server.rb b/spec/tiny_server.rb index 7e6ef3a809..848e2f1133 100644 --- a/spec/tiny_server.rb +++ b/spec/tiny_server.rb @@ -25,6 +25,7 @@ require 'singleton' require 'chef/json_compat' require 'open-uri' require 'chef/config' +require 'chef/json_compat' module TinyServer @@ -152,7 +153,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..14dcc60467 100644 --- a/spec/unit/api_client_spec.rb +++ b/spec/unit/api_client_spec.rb @@ -92,7 +92,7 @@ describe Chef::ApiClient do before(:each) do @client.name("black") @client.public_key("crowes") - @json = @client.to_json + @json = Chef::JSONCompat.to_json(@client) end it "serializes as a JSON object" do @@ -117,7 +117,7 @@ describe Chef::ApiClient do it "includes the private key when present" do @client.private_key("monkeypants") - @client.to_json.should include(%q{"private_key":"monkeypants"}) + Chef::JSONCompat.to_json(@client).should include(%q{"private_key":"monkeypants"}) end it "does not include the private key if not present" do @@ -135,7 +135,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 c29521806a..66681b85ab 100644 --- a/spec/unit/config_fetcher_spec.rb +++ b/spec/unit/config_fetcher_spec.rb @@ -1,7 +1,7 @@ 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/metadata_spec.rb b/spec/unit/cookbook/metadata_spec.rb index 8e98610183..24c9a48b81 100644 --- a/spec/unit/cookbook/metadata_spec.rb +++ b/spec/unit/cookbook/metadata_spec.rb @@ -582,7 +582,7 @@ describe Chef::Cookbook::Metadata do @meta.version "1.2.3" end - describe "serialize" do + describe "#to_json" do before(:each) do @serial = Chef::JSONCompat.from_json(@meta.to_json) end @@ -613,11 +613,15 @@ describe Chef::Cookbook::Metadata do @serial[t].should == @meta.send(t.to_sym) end end + + it "should produce the same output from to_json and Chef::JSONCompat" do + expect(@meta.to_json).to eq(Chef::JSONCompat.to_json(@meta)) + end end - describe "deserialize" do + describe "::from_json" do before(:each) do - @deserial = Chef::Cookbook::Metadata.from_json(@meta.to_json) + @deserial = Chef::Cookbook::Metadata.from_json(Chef::JSONCompat.to_json(@meta)) end it "should deserialize to a Chef::Cookbook::Metadata object" do diff --git a/spec/unit/cookbook_loader_spec.rb b/spec/unit/cookbook_loader_spec.rb index 4c21c124e0..2bcba07eb2 100644 --- a/spec/unit/cookbook_loader_spec.rb +++ b/spec/unit/cookbook_loader_spec.rb @@ -181,7 +181,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..ea36dcc6d8 100644 --- a/spec/unit/cookbook_version_spec.rb +++ b/spec/unit/cookbook_version_spec.rb @@ -420,6 +420,10 @@ describe Chef::CookbookVersion do end end + include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + let(:subject) { Chef::CookbookVersion.new("tatft", '/tmp/blah') } + end + end end diff --git a/spec/unit/data_bag_item_spec.rb b/spec/unit/data_bag_item_spec.rb index ead0dadfa2..f28450ef72 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(:subject) { @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 4ac843c869..b1c4b0ea6f 100644 --- a/spec/unit/data_bag_spec.rb +++ b/spec/unit/data_bag_spec.rb @@ -58,7 +58,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 @@ -73,6 +73,10 @@ describe Chef::DataBag do end end + include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + let(:subject) { @data_bag } + end + end describe "when saving" do diff --git a/spec/unit/deprecation_spec.rb b/spec/unit/deprecation_spec.rb index 8617cb3cb3..6a405c2bb6 100644 --- a/spec/unit/deprecation_spec.rb +++ b/spec/unit/deprecation_spec.rb @@ -46,7 +46,7 @@ describe Chef::Deprecation do end method_snapshot_file = File.join(CHEF_SPEC_DATA, "file-providers-method-snapshot-chef-11-4.json") - method_snapshot = JSON.parse(File.open(method_snapshot_file).read()) + method_snapshot = Chef::JSONCompat.parse(File.open(method_snapshot_file).read()) method_snapshot.each do |class_name, old_methods| class_object = class_from_string(class_name) diff --git a/spec/unit/encrypted_data_bag_item_spec.rb b/spec/unit/encrypted_data_bag_item_spec.rb index 1e662a0b7c..998b493514 100644 --- a/spec/unit/encrypted_data_bag_item_spec.rb +++ b/spec/unit/encrypted_data_bag_item_spec.rb @@ -100,6 +100,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 2 (JSON+aes-256-cbc+hmac-sha256+random iv) encrypted value" do let(:encrypted_value) do @@ -112,6 +123,8 @@ describe Chef::EncryptedDataBagItem::Decryptor do Base64.encode64(raw_hmac) end + include_examples "decryption examples" + it "rejects the data if the hmac is wrong" do encrypted_value["hmac"] = bogus_hmac lambda { decryptor.for_decrypted_item }.should raise_error(Chef::EncryptedDataBagItem::DecryptionFailure) @@ -134,13 +147,7 @@ describe Chef::EncryptedDataBagItem::Decryptor do decryptor.should be_a_kind_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 0e230e46ed..bb4ece0a47 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(:subject) { @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 @@ -420,7 +424,7 @@ describe Chef::Environment do "description" => "desc", "chef_type" => "environment" } - IO.should_receive(:read).with(File.join(Chef::Config[:environment_path], 'foo.json')).and_return(JSON.dump(environment_hash)) + IO.should_receive(:read).with(File.join(Chef::Config[:environment_path], 'foo.json')).and_return(Chef::JSONCompat.to_json(environment_hash)) environment = Chef::Environment.load('foo') environment.should be_a_kind_of(Chef::Environment) diff --git a/spec/unit/exceptions_spec.rb b/spec/unit/exceptions_spec.rb index 3e7b1ba93f..c7bfc63e6b 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(:subject) { exception } + end + end end end diff --git a/spec/unit/json_compat_spec.rb b/spec/unit/json_compat_spec.rb index cce31b0c7c..2d10df874f 100644 --- a/spec/unit/json_compat_spec.rb +++ b/spec/unit/json_compat_spec.rb @@ -21,49 +21,90 @@ require 'chef/json_compat' describe Chef::JSONCompat do - describe "with JSON containing an existing class" do - let(:json){'{"json_class": "Chef::Role"}'} + describe "#from_json with JSON containing an existing class" do + let(:json) { '{"json_class": "Chef::Role"}' } + it "returns an instance of the class instead of a Hash" do - Chef::JSONCompat.from_json(json).class.should eq Chef::Role + expect(Chef::JSONCompat.from_json(json).class).to eq Chef::Role + end + end + + describe "#from_json with JSON containing comments" do + let(:json) { %Q{{\n/* comment */\n// comment 2\n"json_class": "Chef::Role"}} } + + it "returns an instance of the class instead of a Hash" do + expect(Chef::JSONCompat.from_json(json).class).to eq Chef::Role + end + end + + describe "#parse with JSON containing comments" do + let(:json) { %Q{{\n/* comment */\n// comment 2\n"json_class": "Chef::Role"}} } + + it "returns a Hash" do + expect(Chef::JSONCompat.parse(json).class).to eq Hash end end describe 'with JSON containing "Chef::Sandbox" as a json_class value' do require 'chef/sandbox' # Only needed for this test - let(:json){'{"json_class": "Chef::Sandbox", "arbitrary": "data"}'} + + let(:json) { '{"json_class": "Chef::Sandbox", "arbitrary": "data"}' } + it "returns a Hash, because Chef::Sandbox is a dummy class" do - Chef::JSONCompat.from_json(json).should eq({"json_class" => "Chef::Sandbox", "arbitrary" => "data"}) + expect(Chef::JSONCompat.from_json(json)).to eq({"json_class" => "Chef::Sandbox", "arbitrary" => "data"}) end end - describe "with a file with 300 or less nested entries" do - before(:all) do - @json = IO.read(File.join(CHEF_SPEC_DATA, 'big_json.json')) - @hash = Chef::JSONCompat.from_json(@json) + describe "when pretty printing an object that defines #to_json" do + class Foo + def to_json(*a) + Chef::JSONCompat.to_json({'bar' => {'baz' => 5678}}, *a) + end end + it "should work" do + f = Foo.new + expect(Chef::JSONCompat.to_json_pretty(f)).to eql("{\n \"bar\": {\n \"baz\": 5678\n }\n}\n") + end + + include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + let(:subject) { Foo.new } + end + end + + describe "with a file with 300 or less nested entries" do + let(:json) { IO.read(File.join(CHEF_SPEC_DATA, 'big_json.json')) } + let(:hash) { Chef::JSONCompat.from_json(json) } + describe "when a big json file is loaded" do it "should create a Hash from the file" do - @hash.should be_kind_of(Hash) + expect(hash).to be_kind_of(Hash) end + it "should has 'test' as a 300th nested value" do - @hash['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key'].should == 'test' + expect(hash['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']).to eq('test') end end end + describe "with a file with more than 300 nested entries" do - before(:all) do - @json = IO.read(File.join(CHEF_SPEC_DATA, 'big_json_plus_one.json')) - @hash = Chef::JSONCompat.from_json(@json, {:max_nesting => 301}) - end + let(:json) { IO.read(File.join(CHEF_SPEC_DATA, 'big_json_plus_one.json')) } + let(:hash) { Chef::JSONCompat.from_json(json, {:max_nesting => 301}) } describe "when a big json file is loaded" do it "should create a Hash from the file" do - @hash.should be_kind_of(Hash) + expect(hash).to be_kind_of(Hash) end + it "should has 'test' as a 301st nested value" do - @hash['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key'].should == 'test' + expect(hash['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']).to eq('test') 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_metadata_from_file_spec.rb b/spec/unit/knife/cookbook_metadata_from_file_spec.rb index 68ab6740ab..3a1dd9b21e 100644 --- a/spec/unit/knife/cookbook_metadata_from_file_spec.rb +++ b/spec/unit/knife/cookbook_metadata_from_file_spec.rb @@ -27,7 +27,6 @@ describe Chef::Knife::CookbookMetadataFromFile do @tgt = File.expand_path(File.join(CHEF_SPEC_DATA, "metadata", "quick_start", "metadata.json")) @knife = Chef::Knife::CookbookMetadataFromFile.new @knife.name_args = [ @src ] - @knife.stub(:to_json_pretty).and_return(true) @md = Chef::Cookbook::Metadata.new Chef::Cookbook::Metadata.stub(:new).and_return(@md) $stdout.stub(:write) diff --git a/spec/unit/knife/cookbook_site_download_spec.rb b/spec/unit/knife/cookbook_site_download_spec.rb index faf58f7bc7..1b0f9f53d3 100644 --- a/spec/unit/knife/cookbook_site_download_spec.rb +++ b/spec/unit/knife/cookbook_site_download_spec.rb @@ -26,7 +26,8 @@ describe Chef::Knife::CookbookSiteDownload do @knife.name_args = ['apache2'] @noauth_rest = double('no auth rest') @stdout = StringIO.new - @cookbook_api_url = 'http://cookbooks.opscode.com/api/v1/cookbooks' + @stderr = StringIO.new + @cookbook_api_url = 'https://supermarket.getchef.com/api/v1/cookbooks' @version = '1.0.2' @version_us = @version.gsub '.', '_' @current_data = { 'deprecated' => false, diff --git a/spec/unit/knife/cookbook_site_share_spec.rb b/spec/unit/knife/cookbook_site_share_spec.rb index 28b2a509fd..77dce9b175 100644 --- a/spec/unit/knife/cookbook_site_share_spec.rb +++ b/spec/unit/knife/cookbook_site_share_spec.rb @@ -108,16 +108,16 @@ describe Chef::Knife::CookbookSiteShare do File.stub(:open).and_return(true) end - it 'should post the cookbook to "http://cookbooks.opscode.com"' do - response_text = {:uri => 'http://cookbooks.opscode.com/cookbooks/cookbook_name'}.to_json + it 'should post the cookbook to "https://supermarket.getchef.com"' do + response_text = {:uri => 'https://supermarket.getchef.com/cookbooks/cookbook_name'}.to_json @upload_response.stub(:body).and_return(response_text) @upload_response.stub(:code).and_return(201) - Chef::CookbookSiteStreamingUploader.should_receive(:post).with(/cookbooks\.opscode\.com/, anything(), anything(), anything()) + Chef::CookbookSiteStreamingUploader.should_receive(:post).with(/supermarket\.getchef\.com/, anything(), anything(), anything()) @knife.run 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 +125,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 +133,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 9cabc59719..57c1934439 100644 --- a/spec/unit/knife/core/bootstrap_context_spec.rb +++ b/spec/unit/knife/core/bootstrap_context_spec.rb @@ -116,13 +116,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/data_bag_from_file_spec.rb b/spec/unit/knife/data_bag_from_file_spec.rb index 8537045164..0e53a67619 100644 --- a/spec/unit/knife/data_bag_from_file_spec.rb +++ b/spec/unit/knife/data_bag_from_file_spec.rb @@ -21,7 +21,6 @@ require 'spec_helper' require 'chef/data_bag_item' require 'chef/encrypted_data_bag_item' require 'tempfile' -require 'json' Chef::Knife::DataBagFromFile.load_deps @@ -46,7 +45,7 @@ describe Chef::Knife::DataBagFromFile do "greeting" => "hello", "nested" => { "a1" => [1, 2, 3], "a2" => { "b1" => true }} } - @db_file.write(@plain_data.to_json) + @db_file.write(Chef::JSONCompat.to_json(@plain_data)) @db_file.flush @knife.instance_variable_set(:@name_args, ['bag_name', @db_file.path]) end diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb index 21a978a9c9..ccecfca2f0 100644 --- a/spec/unit/node_spec.rb +++ b/spec/unit/node_spec.rb @@ -762,6 +762,10 @@ 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(:subject) { node.from_file(File.expand_path("nodes/test.example.com.rb", CHEF_SPEC_DATA)) } + end end describe "to_s" do 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/env/windows_spec.rb b/spec/unit/provider/env/windows_spec.rb index 2ea137c1d9..84582d8b4d 100644 --- a/spec/unit/provider/env/windows_spec.rb +++ b/spec/unit/provider/env/windows_spec.rb @@ -53,7 +53,7 @@ describe Chef::Provider::Env::Windows, :windows_only do end it "should update the ruby ENV object when it updates the value" do - provider.should_receive(:compare_value).and_return(true) + provider.should_receive(:requires_modify_or_create?).and_return(true) new_resource.value("foobar") provider.action_modify expect(ENV['CHEF_WINDOWS_ENV_TEST']).to eql('foobar') @@ -92,7 +92,7 @@ describe Chef::Provider::Env::Windows, :windows_only do end it "replaces Windows system variables" do - provider.should_receive(:compare_value).and_return(true) + provider.should_receive(:requires_modify_or_create?).and_return(true) provider.should_receive(:expand_path).with(system_root).and_return(system_root_value) provider.action_modify expect(ENV['PATH']).to eql(system_root_value) diff --git a/spec/unit/provider/env_spec.rb b/spec/unit/provider/env_spec.rb index 0bc5117e48..f8803f9bb6 100644 --- a/spec/unit/provider/env_spec.rb +++ b/spec/unit/provider/env_spec.rb @@ -88,20 +88,20 @@ describe Chef::Provider::Env do it "should check to see if the values are the same if the key exists" do @provider.key_exists = true - @provider.should_receive(:compare_value).and_return(false) + @provider.should_receive(:requires_modify_or_create?).and_return(false) @provider.action_create end it "should call modify_env if the key exists and values are not equal" do @provider.key_exists = true - @provider.stub(:compare_value).and_return(true) + @provider.stub(:requires_modify_or_create?).and_return(true) @provider.should_receive(:modify_env).and_return(true) @provider.action_create end it "should set the new_resources updated flag when it updates an existing value" do @provider.key_exists = true - @provider.stub(:compare_value).and_return(true) + @provider.stub(:requires_modify_or_create?).and_return(true) @provider.stub(:modify_env).and_return(true) @provider.action_create @new_resource.should be_updated @@ -147,20 +147,20 @@ describe Chef::Provider::Env do end it "should call modify_group if the key exists and values are not equal" do - @provider.should_receive(:compare_value).and_return(true) + @provider.should_receive(:requires_modify_or_create?).and_return(true) @provider.should_receive(:modify_env).and_return(true) @provider.action_modify end it "should set the new resources updated flag to true if modify_env is called" do - @provider.stub(:compare_value).and_return(true) + @provider.stub(:requires_modify_or_create?).and_return(true) @provider.stub(:modify_env).and_return(true) @provider.action_modify @new_resource.should be_updated end it "should not call modify_env if the key exists but the values are equal" do - @provider.should_receive(:compare_value).and_return(false) + @provider.should_receive(:requires_modify_or_create?).and_return(false) @provider.should_not_receive(:modify_env) @provider.action_modify end @@ -198,9 +198,31 @@ describe Chef::Provider::Env do @provider.delete_element.should eql(true) @new_resource.should be_updated end + + context "when new_resource's value contains the delimiter" do + it "should return false if all the elements are deleted" do + # This indicates that the entire key needs to be deleted + @new_resource.value("C:/foo/bin;C:/bar/bin") + @provider.delete_element.should eql(false) + @new_resource.should_not be_updated # This will be updated in action_delete + end + + it "should return true if any, but not all, of the elements are deleted" do + @new_resource.value("C:/foo/bin;C:/notbaz/bin") + @provider.should_receive(:create_env) + @provider.delete_element.should eql(true) + @new_resource.should be_updated + end + + it "should return true if none of the elements are deleted" do + @new_resource.value("C:/notfoo/bin;C:/notbaz/bin") + @provider.delete_element.should eql(true) + @new_resource.should_not be_updated + end + end end - describe "compare_value" do + describe "requires_modify_or_create?" do before(:each) do @new_resource.value("C:/bar") @current_resource = @new_resource.clone @@ -208,25 +230,68 @@ describe Chef::Provider::Env do end it "should return false if the values are equal" do - @provider.compare_value.should be_false + @provider.requires_modify_or_create?.should be_false end it "should return true if the values not are equal" do @new_resource.value("C:/elsewhere") - @provider.compare_value.should be_true + @provider.requires_modify_or_create?.should be_true end it "should return false if the current value contains the element" do @new_resource.delim(";") @current_resource.value("C:/bar;C:/foo;C:/baz") - @provider.compare_value.should be_false + @provider.requires_modify_or_create?.should be_false end it "should return true if the current value does not contain the element" do @new_resource.delim(";") @current_resource.value("C:/biz;C:/foo/bin;C:/baz") - @provider.compare_value.should be_true + @provider.requires_modify_or_create?.should be_true + end + + context "when new_resource's value contains the delimiter" do + it "should return false if all the current values are contained" do + @new_resource.value("C:/biz;C:/baz") + @new_resource.delim(";") + @current_resource.value("C:/biz;C:/foo/bin;C:/baz") + @provider.requires_modify_or_create?.should be_false + end + + it "should return true if any of the new values are not contained" do + @new_resource.value("C:/biz;C:/baz;C:/bin") + @new_resource.delim(";") + @current_resource.value("C:/biz;C:/foo/bin;C:/baz") + @provider.requires_modify_or_create?.should be_true + end + end + end + + describe "modify_env" do + before(:each) do + @provider.stub(:create_env).and_return(true) + @new_resource.delim ";" + + @current_resource = Chef::Resource::Env.new("FOO") + @current_resource.value "C:/foo/bin" + @provider.current_resource = @current_resource + end + + it "should not modify the variable passed to the resource" do + new_value = "C:/bar/bin" + passed_value = new_value.dup + @new_resource.value(passed_value) + @provider.modify_env + passed_value.should == new_value + end + + it "should only add values not already contained when a delimiter is provided" do + @new_resource.value("C:/foo;C:/bar;C:/baz") + @new_resource.delim(";") + @current_resource.value("C:/foo/bar;C:/bar;C:/baz") + @provider.modify_env + @new_resource.value.should eq("C:/foo;C:/foo/bar;C:/bar;C:/baz") 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/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_collection_spec.rb b/spec/unit/resource_collection_spec.rb index cf62f5ff40..cc9cc83923 100644 --- a/spec/unit/resource_collection_spec.rb +++ b/spec/unit/resource_collection_spec.rb @@ -245,12 +245,16 @@ 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(:subject) { @rc } + end end describe "self.from_json" do it "should deserialize itself from json" 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 70941e4e82..d10f3ca27b 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(:subject) { @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 05ebf282db..42819cda21 100644 --- a/spec/unit/role_spec.rb +++ b/spec/unit/role_spec.rb @@ -215,6 +215,10 @@ describe Chef::Role do end end + + include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + let(:subject) { @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..d2f0e01811 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(:subject) { @run_list } end end diff --git a/spec/unit/user_spec.rb b/spec/unit/user_spec.rb index 08bde33d7b..055512444b 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(:subject) { @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 diff --git a/spec/unit/util/dsc/local_configuration_manager_spec.rb b/spec/unit/util/dsc/local_configuration_manager_spec.rb index fb6664bd40..eb27e9e94e 100644 --- a/spec/unit/util/dsc/local_configuration_manager_spec.rb +++ b/spec/unit/util/dsc/local_configuration_manager_spec.rb @@ -32,7 +32,7 @@ EOH } let(:no_whatif_lcm_output) { <<-EOH -Start-DscConfiguration : A parameter cannot be found that matches parameter name 'whatif'. +Start-DscConfiguration : A parameter cannot be found\r\n that matches parameter name 'whatif'. At line:1 char:123 + run-somecommand -whatif + ~~~~~~~~ @@ -77,8 +77,13 @@ EOH let(:lcm_standard_error) { no_whatif_lcm_output } let(:lcm_cmdlet_success) { false } + it 'returns true when passed to #whatif_not_supported?' do + expect(lcm.send(:whatif_not_supported?, no_whatif_lcm_output)).to be_true + end + it 'should should return a (possibly empty) array of ResourceInfo instances' do expect(Chef::Log).to receive(:warn) + expect(lcm).to receive(:whatif_not_supported?).and_call_original test_configuration_result = nil expect {test_configuration_result = lcm.test_configuration('config')}.not_to raise_error expect(test_configuration_result.class).to be(Array) @@ -92,7 +97,7 @@ EOH it 'should log a warning if the message is formatted as expected when a resource import failure occurs' do expect(Chef::Log).to receive(:warn) - expect(lcm).to receive(:output_has_dsc_module_failure?).and_call_original + expect(lcm).to receive(:dsc_module_import_failure?).and_call_original test_configuration_result = nil expect {test_configuration_result = lcm.test_configuration('config')}.not_to raise_error end @@ -105,29 +110,29 @@ EOH end end - context 'that fails due to an PowerShell cmdlet error that cannot be handled' do + context 'that fails due to an unknown PowerShell cmdlet error' do let(:lcm_standard_output) { 'some output' } let(:lcm_standard_error) { 'Abort, Retry, Fail?' } let(:lcm_cmdlet_success) { false } - it 'should raise a Chef::Exceptions::PowershellCmdletException' do - expect(Chef::Log).not_to receive(:warn) - expect(lcm).to receive(:output_has_dsc_module_failure?).and_call_original - expect {lcm.test_configuration('config')}.to raise_error(Chef::Exceptions::PowershellCmdletException) + it 'should log a warning' do + expect(Chef::Log).to receive(:warn) + expect(lcm).to receive(:dsc_module_import_failure?).and_call_original + expect {lcm.test_configuration('config')}.not_to raise_error end end end it 'should identify a correctly formatted error message as a resource import failure' do - expect(lcm.send(:output_has_dsc_module_failure?, dsc_resource_import_failure_output)).to be(true) + expect(lcm.send(:dsc_module_import_failure?, dsc_resource_import_failure_output)).to be(true) end it 'should not identify an incorrectly formatted error message as a resource import failure' do - expect(lcm.send(:output_has_dsc_module_failure?, dsc_resource_import_failure_output.gsub('module', 'gibberish'))).to be(false) + expect(lcm.send(:dsc_module_import_failure?, dsc_resource_import_failure_output.gsub('module', 'gibberish'))).to be(false) end it 'should not identify a message without a CimException reference as a resource import failure' do - expect(lcm.send(:output_has_dsc_module_failure?, dsc_resource_import_failure_output.gsub('CimException', 'ArgumentException'))).to be(false) + expect(lcm.send(:dsc_module_import_failure?, dsc_resource_import_failure_output.gsub('CimException', 'ArgumentException'))).to be(false) end end end |