diff options
author | sawanoboly <sawanoboriyu@higanworks.com> | 2014-08-12 15:56:16 +0900 |
---|---|---|
committer | sawanoboly <sawanoboriyu@higanworks.com> | 2014-08-12 15:56:16 +0900 |
commit | 5c94009b6053e7ae71d528485f3560e2bf8a7851 (patch) | |
tree | 4070625ed40df73783c3fda3149ed526fbc34447 /lib | |
parent | bea385b45620c5c5769d8680758181770b3c148f (diff) | |
parent | f71cb384f3bbdca9093af76830b8ae3949d54c4e (diff) | |
download | chef-5c94009b6053e7ae71d528485f3560e2bf8a7851.tar.gz |
Merge remote-tracking branch 'upstream/master' into prevew_archive_before_site_share
Diffstat (limited to 'lib')
61 files changed, 692 insertions, 228 deletions
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..b2435d8201 100644 --- a/lib/chef/chef_fs/chef_fs_data_store.rb +++ b/lib/chef/chef_fs/chef_fs_data_store.rb @@ -269,7 +269,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) 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..43e8b276e0 100644 --- a/lib/chef/chef_fs/command_line.rb +++ b/lib/chef/chef_fs/command_line.rb @@ -251,7 +251,7 @@ 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) sorted_json = sort_keys(parsed_json) JSON.pretty_generate(sorted_json) end diff --git a/lib/chef/chef_fs/file_system.rb b/lib/chef/chef_fs/file_system.rb index ffbe274864..4d15d7af33 100644 --- a/lib/chef/chef_fs/file_system.rb +++ b/lib/chef/chef_fs/file_system.rb @@ -369,7 +369,7 @@ class Chef end else if dest_entry.dir? - ui.error("File #{src_path} is a directory while file #{dest_path} is a regular file\n") if ui + ui.error("File #{src_path} is a regular file while file #{dest_path} is a directory\n") if ui return else diff --git a/lib/chef/chef_fs/file_system/acl_entry.rb b/lib/chef/chef_fs/file_system/acl_entry.rb index 8edc02d5c5..1bd03a6095 100644 --- a/lib/chef/chef_fs/file_system/acl_entry.rb +++ b/lib/chef/chef_fs/file_system/acl_entry.rb @@ -37,7 +37,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), 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..6ccdc2cf5f 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 @@ -41,7 +41,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)) rescue Chef::Log.error("Could not read #{path_for_printing} into a Chef object: #{$!}") end @@ -60,10 +60,10 @@ class Chef end def minimize(file_contents, entry) - object = JSONCompat.from_json(file_contents, :create_additions => false) + object = Chef::JSONCompat.from_json(file_contents) object = data_handler.normalize(object, entry) object = data_handler.minimize(object, entry) - JSONCompat.to_json_pretty(object) + Chef::JSONCompat.to_json_pretty(object) end def children 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..672fa444f1 100644 --- a/lib/chef/chef_fs/file_system/rest_list_dir.rb +++ b/lib/chef/chef_fs/file_system/rest_list_dir.rb @@ -61,8 +61,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) + 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..67252a6f2f 100644 --- a/lib/chef/chef_fs/file_system/rest_list_entry.rb +++ b/lib/chef/chef_fs/file_system/rest_list_entry.rb @@ -128,8 +128,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) + 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 +145,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) + 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.rb b/lib/chef/config.rb index 93cd05d0ef..9d465a3cea 100644 --- a/lib/chef/config.rb +++ b/lib/chef/config.rb @@ -432,7 +432,8 @@ class Chef # format. Version "2" is available which adds encrypt-then-mac protection. # To maintain compatibility, versions other than 1 must be opt-in. # - # Set this to `2` if you have chef-client 11.6.0+ in your infrastructure: + # Set this to `2` if you have chef-client 11.6.0+ in your infrastructure. + # Set this to `3` if you have chef-client 11.?.0+, ruby 2 and OpenSSL >= 1.0.1 in your infrastructure. (TODO) default :data_bag_encrypt_version, 1 # When reading data bag items, any supported version is accepted. However, 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..8d3f8b84aa 100644 --- a/lib/chef/cookbook/metadata.rb +++ b/lib/chef/cookbook/metadata.rb @@ -24,6 +24,7 @@ require 'chef/mixin/params_validate' require 'chef/log' require 'chef/version_class' require 'chef/version_constraint' +require 'chef/json_compat' class Chef class Cookbook @@ -242,8 +243,8 @@ class Chef # versions<Array>:: Returns the list of versions for the platform def supports(platform, *version_args) version = new_args_format(:supports, platform, version_args) - normalized_version = normalize_version_constraint(:supports, platform, version) - @platforms[platform] = normalized_version + constraint = validate_version_constraint(:supports, platform, version) + @platforms[platform] = constraint.to_s @platforms[platform] end @@ -259,8 +260,8 @@ class Chef # versions<Array>:: Returns the list of versions for the platform def depends(cookbook, *version_args) version = new_args_format(:depends, cookbook, version_args) - normalized_version = normalize_version_constraint(:depends, cookbook, version) - @dependencies[cookbook] = normalized_version + constraint = validate_version_constraint(:depends, cookbook, version) + @dependencies[cookbook] = constraint.to_s @dependencies[cookbook] end @@ -276,8 +277,8 @@ class Chef # versions<Array>:: Returns the list of versions for the platform def recommends(cookbook, *version_args) version = new_args_format(:recommends, cookbook, version_args) - normalized_version = normalize_version_constraint(:recommends, cookbook, version) - @recommendations[cookbook] = normalized_version + constraint = validate_version_constraint(:recommends, cookbook, version) + @recommendations[cookbook] = constraint.to_s @recommendations[cookbook] end @@ -293,8 +294,8 @@ class Chef # versions<Array>:: Returns the list of versions for the platform def suggests(cookbook, *version_args) version = new_args_format(:suggests, cookbook, version_args) - normalized_version = normalize_version_constraint(:suggests, cookbook, version) - @suggestions[cookbook] = normalized_version + constraint = validate_version_constraint(:suggests, cookbook, version) + @suggestions[cookbook] = constraint.to_s @suggestions[cookbook] end @@ -310,8 +311,8 @@ class Chef # versions<Array>:: Returns the list of versions for the platform def conflicts(cookbook, *version_args) version = new_args_format(:conflicts, cookbook, version_args) - normalized_version = normalize_version_constraint(:conflicts, cookbook, version) - @conflicting[cookbook] = normalized_version + constraint = validate_version_constraint(:conflicts, cookbook, version) + @conflicting[cookbook] = constraint.to_s @conflicting[cookbook] end @@ -331,8 +332,8 @@ class Chef # versions<Array>:: Returns the list of versions for the platform def provides(cookbook, *version_args) version = new_args_format(:provides, cookbook, version_args) - normalized_version = normalize_version_constraint(:provides, cookbook, version) - @providing[cookbook] = normalized_version + constraint = validate_version_constraint(:provides, cookbook, version) + @providing[cookbook] = constraint.to_s @providing[cookbook] end @@ -347,8 +348,8 @@ class Chef # versions<Array>:: Returns the list of versions for the platform def replaces(cookbook, *version_args) version = new_args_format(:replaces, cookbook, version_args) - normalized_version = normalize_version_constraint(:replaces, cookbook, version) - @replacing[cookbook] = normalized_version + constraint = validate_version_constraint(:replaces, cookbook, version) + @replacing[cookbook] = constraint.to_s @replacing[cookbook] end @@ -441,7 +442,7 @@ class Chef end def to_json(*a) - self.to_hash.to_json(*a) + Chef::JSONCompat.to_json(to_hash, *a) end def self.from_hash(o) @@ -533,11 +534,6 @@ INVALID raise Exceptions::InvalidVersionConstraint, msg end - def normalize_version_constraint(caller_name, dep_name, constraint_str) - version_constraint = validate_version_constraint(caller_name, dep_name, constraint_str) - "#{version_constraint.op} #{version_constraint.raw_version}" - end - # Verify that the given array is an array of strings # # Raise an exception if the members of the array are not Strings @@ -656,6 +652,5 @@ INVALID end end - end end diff --git a/lib/chef/cookbook/syntax_check.rb b/lib/chef/cookbook/syntax_check.rb index 9fee7c2a54..092fb47eba 100644 --- a/lib/chef/cookbook/syntax_check.rb +++ b/lib/chef/cookbook/syntax_check.rb @@ -125,7 +125,7 @@ class Chef end def template_files - remove_ignored_files Dir[File.join(cookbook_path, '**', '*.erb')] + remove_ignored_files Dir[File.join(cookbook_path, '**/templates/**', '*.erb')] end def untested_template_files diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb index 778a6043bf..4482f778c1 100644 --- a/lib/chef/cookbook_version.rb +++ b/lib/chef/cookbook_version.rb @@ -405,7 +405,6 @@ class Chef records_by_pref[best_pref] end - # Given a node, segment and path (filename or directory name), # return the priority-ordered list of preference locations to # look. @@ -458,9 +457,9 @@ class Chef end def to_json(*a) - result = self.to_hash + result = to_hash result['json_class'] = self.class.name - result.to_json(*a) + Chef::JSONCompat.to_json(result, *a) end def self.json_create(o) diff --git a/lib/chef/data_bag.rb b/lib/chef/data_bag.rb index 639d71a74d..99808d0609 100644 --- a/lib/chef/data_bag.rb +++ b/lib/chef/data_bag.rb @@ -54,16 +54,16 @@ class Chef def to_hash result = { - "name" => @name, + 'name' => @name, 'json_class' => self.class.name, - "chef_type" => "data_bag", + 'chef_type' => 'data_bag', } result end # 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 @@ -83,11 +83,15 @@ class Chef def self.list(inflate=false) if Chef::Config[:solo] - unless File.directory?(Chef::Config[:data_bag_path]) - raise Chef::Exceptions::InvalidDataBagPath, "Data bag path '#{Chef::Config[:data_bag_path]}' is invalid" - end + paths = Array(Chef::Config[:data_bag_path]) + names = [] + paths.each do |path| + unless File.directory?(path) + raise Chef::Exceptions::InvalidDataBagPath, "Data bag path '#{path}' is invalid" + end - names = Dir.glob(File.join(Chef::Config[:data_bag_path], "*")).map{|f|File.basename(f)}.sort + names += Dir.glob(File.join(path, "*")).map{|f|File.basename(f)}.sort + end names.inject({}) {|h, n| h[n] = n; h} else if inflate @@ -105,15 +109,25 @@ class Chef # Load a Data Bag by name via either the RESTful API or local data_bag_path if run in solo mode def self.load(name) if Chef::Config[:solo] - unless File.directory?(Chef::Config[:data_bag_path]) - raise Chef::Exceptions::InvalidDataBagPath, "Data bag path '#{Chef::Config[:data_bag_path]}' is invalid" - end + paths = Array(Chef::Config[:data_bag_path]) + data_bag = {} + paths.each do |path| + unless File.directory?(path) + raise Chef::Exceptions::InvalidDataBagPath, "Data bag path '#{path}' is invalid" + end + + Dir.glob(File.join(path, name.to_s, "*.json")).inject({}) do |bag, f| + item = Chef::JSONCompat.from_json(IO.read(f)) - Dir.glob(File.join(Chef::Config[:data_bag_path], "#{name}", "*.json")).inject({}) do |bag, f| - item = Chef::JSONCompat.from_json(IO.read(f)) - bag[item['id']] = item - bag + # Check if we have multiple items with similar names (ids) and raise if their content differs + if data_bag.has_key?(item["id"]) && data_bag[item["id"]] != item + raise Chef::Exceptions::DuplicateDataBagItem, "Data bag '#{name}' has items with the same name '#{item["id"]}' but different content." + else + data_bag[item["id"]] = item + end + end end + return data_bag else Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("data/#{name}") end @@ -150,4 +164,3 @@ class Chef end end - diff --git a/lib/chef/data_bag_item.rb b/lib/chef/data_bag_item.rb index 07dd15a1dc..6f0ae65478 100644 --- a/lib/chef/data_bag_item.rb +++ b/lib/chef/data_bag_item.rb @@ -112,13 +112,13 @@ class Chef # Serialize this object as a hash def to_json(*a) result = { - "name" => self.object_name, + "name" => object_name, "json_class" => self.class.name, - "chef_type" => "data_bag_item", - "data_bag" => self.data_bag, - "raw_data" => self.raw_data + "chef_type" => "data_bag_item", + "data_bag" => data_bag, + "raw_data" => raw_data } - result.to_json(*a) + Chef::JSONCompat.to_json(result, *a) end def self.from_hash(h) @@ -210,5 +210,3 @@ class Chef end end - - diff --git a/lib/chef/encrypted_data_bag_item.rb b/lib/chef/encrypted_data_bag_item.rb index b0d9337212..f722b5dc38 100644 --- a/lib/chef/encrypted_data_bag_item.rb +++ b/lib/chef/encrypted_data_bag_item.rb @@ -48,6 +48,7 @@ require 'open-uri' # class Chef::EncryptedDataBagItem ALGORITHM = 'aes-256-cbc' + AEAD_ALGORITHM = 'aes-256-gcm' # # === Synopsis diff --git a/lib/chef/encrypted_data_bag_item/assertions.rb b/lib/chef/encrypted_data_bag_item/assertions.rb new file mode 100644 index 0000000000..e9acebbd29 --- /dev/null +++ b/lib/chef/encrypted_data_bag_item/assertions.rb @@ -0,0 +1,57 @@ +# +# Author:: Xabier de Zuazo (<xabier@onddo.com>) +# Copyright:: Copyright (c) 2014 Onddo Labs, SL. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/encrypted_data_bag_item/unacceptable_encrypted_data_bag_item_format' +require 'chef/encrypted_data_bag_item/unsupported_cipher' + +class Chef::EncryptedDataBagItem + + class EncryptedDataBagRequirementsFailure < StandardError + end + + module Assertions + + def assert_format_version_acceptable!(format_version) + unless format_version.kind_of?(Integer) and format_version >= Chef::Config[:data_bag_decrypt_minimum_version] + raise UnacceptableEncryptedDataBagItemFormat, + "The encrypted data bag item has format version `#{format_version}', " + + "but the config setting 'data_bag_decrypt_minimum_version' requires version `#{Chef::Config[:data_bag_decrypt_minimum_version]}'" + end + end + + def assert_valid_cipher!(requested_cipher, algorithm) + # In the future, chef may support configurable ciphers. For now, only + # aes-256-cbc and aes-256-gcm are supported. + unless requested_cipher == algorithm + raise UnsupportedCipher, + "Cipher '#{requested_cipher}' is not supported by this version of Chef. Available ciphers: ['#{ALGORITHM}', '#{AEAD_ALGORITHM}']" + end + end + + def assert_aead_requirements_met!(algorithm) + unless OpenSSL::Cipher.method_defined?(:auth_data=) + raise EncryptedDataBagRequirementsFailure, "The used Encrypted Data Bags version requires Ruby >= 1.9" + end + unless OpenSSL::Cipher.ciphers.include?(algorithm) + raise EncryptedDataBagRequirementsFailure, "The used Encrypted Data Bags version requires an OpenSSL version with \"#{algorithm}\" algorithm support" + end + end + + end + +end diff --git a/lib/chef/encrypted_data_bag_item/decryptor.rb b/lib/chef/encrypted_data_bag_item/decryptor.rb index 69b8d62e3b..97a166b932 100644 --- a/lib/chef/encrypted_data_bag_item/decryptor.rb +++ b/lib/chef/encrypted_data_bag_item/decryptor.rb @@ -17,15 +17,14 @@ # require 'yaml' -require 'ffi_yajl' +require 'chef/json_compat' require 'openssl' require 'base64' require 'digest/sha2' 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' require 'chef/encrypted_data_bag_item/decryption_failure' -require 'chef/encrypted_data_bag_item/unsupported_cipher' +require 'chef/encrypted_data_bag_item/assertions' class Chef::EncryptedDataBagItem @@ -37,6 +36,7 @@ class Chef::EncryptedDataBagItem # to create an instance of the appropriate strategy for the given encrypted # data bag value. module Decryptor + extend Chef::EncryptedDataBagItem::Assertions # Detects the encrypted data bag item format version and instantiates a # decryptor object for that version. Call #for_decrypted_item on the @@ -45,6 +45,8 @@ class Chef::EncryptedDataBagItem format_version = format_version_of(encrypted_value) assert_format_version_acceptable!(format_version) case format_version + when 3 + Version3Decryptor.new(encrypted_value, key) when 2 Version2Decryptor.new(encrypted_value, key) when 1 @@ -65,15 +67,8 @@ class Chef::EncryptedDataBagItem end end - def self.assert_format_version_acceptable!(format_version) - unless format_version.kind_of?(Integer) and format_version >= Chef::Config[:data_bag_decrypt_minimum_version] - raise UnacceptableEncryptedDataBagItemFormat, - "The encrypted data bag item has format version `#{format_version}', " + - "but the config setting 'data_bag_decrypt_minimum_version' requires version `#{Chef::Config[:data_bag_decrypt_minimum_version]}'" - end - end - class Version0Decryptor + include Chef::EncryptedDataBagItem::Assertions attr_reader :encrypted_data attr_reader :key @@ -83,6 +78,11 @@ class Chef::EncryptedDataBagItem @key = key end + # Returns the used decryption algorithm + def algorithm + ALGORITHM + end + def for_decrypted_item YAML.load(decrypted_data) end @@ -102,7 +102,7 @@ class Chef::EncryptedDataBagItem def openssl_decryptor @openssl_decryptor ||= begin - d = OpenSSL::Cipher::Cipher.new(ALGORITHM) + d = OpenSSL::Cipher.new(algorithm) d.decrypt d.pkcs5_keyivgen(key) d @@ -110,7 +110,7 @@ class Chef::EncryptedDataBagItem end end - class Version1Decryptor + class Version1Decryptor < Version0Decryptor attr_reader :encrypted_data attr_reader :key @@ -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. @@ -148,24 +148,16 @@ class Chef::EncryptedDataBagItem def openssl_decryptor @openssl_decryptor ||= begin - assert_valid_cipher! - d = OpenSSL::Cipher::Cipher.new(ALGORITHM) + assert_valid_cipher!(@encrypted_data["cipher"], algorithm) + d = OpenSSL::Cipher.new(algorithm) d.decrypt + # We must set key before iv: https://bugs.ruby-lang.org/issues/8221 d.key = Digest::SHA256.digest(key) d.iv = iv d end end - def assert_valid_cipher! - # In the future, chef may support configurable ciphers. For now, only - # aes-256-cbc is supported. - requested_cipher = @encrypted_data["cipher"] - unless requested_cipher == ALGORITHM - raise UnsupportedCipher, - "Cipher '#{requested_cipher}' is not supported by this version of Chef. Available ciphers: ['#{ALGORITHM}']" - end - end end class Version2Decryptor < Version1Decryptor @@ -176,7 +168,7 @@ class Chef::EncryptedDataBagItem end def validate_hmac! - digest = OpenSSL::Digest::Digest.new("sha256") + digest = OpenSSL::Digest.new("sha256") raw_hmac = OpenSSL::HMAC.digest(digest, key, @encrypted_data["encrypted_data"]) if candidate_hmac_matches?(raw_hmac) @@ -197,5 +189,37 @@ class Chef::EncryptedDataBagItem valid == 0 end end + + class Version3Decryptor < Version1Decryptor + + def initialize(encrypted_data, key) + super + assert_aead_requirements_met!(algorithm) + end + + # Returns the used decryption algorithm + def algorithm + AEAD_ALGORITHM + end + + def auth_tag + auth_tag_b64 = @encrypted_data["auth_tag"] + if auth_tag_b64.nil? + raise DecryptionFailure, "Error decrypting data bag value: invalid authentication tag. Most likely the data is corrupted" + end + Base64.decode64(auth_tag_b64) + end + + def openssl_decryptor + @openssl_decryptor ||= begin + d = super + d.auth_tag = auth_tag + d.auth_data = '' + d + end + end + + end + end end diff --git a/lib/chef/encrypted_data_bag_item/encrypted_data_bag_item_assertions.rb b/lib/chef/encrypted_data_bag_item/encrypted_data_bag_item_assertions.rb new file mode 100644 index 0000000000..3567925844 --- /dev/null +++ b/lib/chef/encrypted_data_bag_item/encrypted_data_bag_item_assertions.rb @@ -0,0 +1,37 @@ +# +# Author:: Xabier de Zuazo (<xabier@onddo.com>) +# Copyright:: Copyright (c) 2014 Onddo Labs, SL. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +class Chef::EncryptedDataBagItem + + class EncryptedDataBagRequirementsFailure < StandardError + end + + module Assertions + + def assert_requirements_met! + unless OpenSSL::Cipher.method_defined?(:auth_data=) + raise EncryptedDataBagRequirementsFailure, "The used Encrypted Data Bags version requires Ruby >= 1.9" + end + unless OpenSSL::Cipher.ciphers.include?(algorithm) + raise EncryptedDataBagRequirementsFailure, "The used Encrypted Data Bags version requires an OpenSSL version with \"#{algorithm}\" algorithm support" + end + end + + end + +end diff --git a/lib/chef/encrypted_data_bag_item/encryption_failure.rb b/lib/chef/encrypted_data_bag_item/encryption_failure.rb new file mode 100644 index 0000000000..380e9bc1f7 --- /dev/null +++ b/lib/chef/encrypted_data_bag_item/encryption_failure.rb @@ -0,0 +1,22 @@ +# +# Author:: Xabier de Zuazo (<xabier@onddo.com>) +# Copyright:: Copyright (c) 2014 Onddo Labs, SL. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +class Chef::EncryptedDataBagItem + class EncryptionFailure < StandardError + end +end diff --git a/lib/chef/encrypted_data_bag_item/encryptor.rb b/lib/chef/encrypted_data_bag_item/encryptor.rb index 9686e84b34..673b52a3c3 100644 --- a/lib/chef/encrypted_data_bag_item/encryptor.rb +++ b/lib/chef/encrypted_data_bag_item/encryptor.rb @@ -22,6 +22,8 @@ require 'openssl' require 'ffi_yajl' require 'chef/encrypted_data_bag_item' require 'chef/encrypted_data_bag_item/unsupported_encrypted_data_bag_item_format' +require 'chef/encrypted_data_bag_item/encryption_failure' +require 'chef/encrypted_data_bag_item/assertions' class Chef::EncryptedDataBagItem @@ -40,9 +42,11 @@ class Chef::EncryptedDataBagItem Version1Encryptor.new(value, secret, iv) when 2 Version2Encryptor.new(value, secret, iv) + when 3 + Version3Encryptor.new(value, secret, iv) else raise UnsupportedEncryptedDataBagItemFormat, - "Invalid encrypted data bag format version `#{format_version}'. Supported versions are '1', '2'" + "Invalid encrypted data bag format version `#{format_version}'. Supported versions are '1', '2', '3'" end end @@ -50,6 +54,8 @@ class Chef::EncryptedDataBagItem attr_reader :key attr_reader :plaintext_data + include Chef::EncryptedDataBagItem::Assertions + # Create a new Encryptor for +data+, which will be encrypted with the given # +key+. # @@ -65,6 +71,11 @@ class Chef::EncryptedDataBagItem @iv = iv && Base64.decode64(iv) end + # Returns the used encryption algorithm + def algorithm + ALGORITHM + end + # Returns a wrapped and encrypted version of +plaintext_data+ suitable for # using as the value in an encrypted data bag item. def for_encrypted_item @@ -72,27 +83,28 @@ class Chef::EncryptedDataBagItem "encrypted_data" => encrypted_data, "iv" => Base64.encode64(iv), "version" => 1, - "cipher" => ALGORITHM + "cipher" => algorithm } end # Generates or returns the IV. def iv - # Generated IV comes from OpenSSL::Cipher::Cipher#random_iv + # Generated IV comes from OpenSSL::Cipher#random_iv # This gets generated when +openssl_encryptor+ gets created. openssl_encryptor if @iv.nil? @iv end - # Generates (and memoizes) an OpenSSL::Cipher::Cipher object and configures + # Generates (and memoizes) an OpenSSL::Cipher object and configures # it for the specified iv and encryption key. def openssl_encryptor @openssl_encryptor ||= begin - encryptor = OpenSSL::Cipher::Cipher.new(ALGORITHM) + encryptor = OpenSSL::Cipher.new(algorithm) encryptor.encrypt + # We must set key before iv: https://bugs.ruby-lang.org/issues/8221 + encryptor.key = Digest::SHA256.digest(key) @iv ||= encryptor.random_iv encryptor.iv = @iv - encryptor.key = Digest::SHA256.digest(key) encryptor end end @@ -125,18 +137,77 @@ class Chef::EncryptedDataBagItem "hmac" => hmac, "iv" => Base64.encode64(iv), "version" => 2, - "cipher" => ALGORITHM + "cipher" => algorithm } end # Generates an HMAC-SHA2-256 of the encrypted data (encrypt-then-mac) def hmac @hmac ||= begin - digest = OpenSSL::Digest::Digest.new("sha256") + digest = OpenSSL::Digest.new("sha256") raw_hmac = OpenSSL::HMAC.digest(digest, key, encrypted_data) Base64.encode64(raw_hmac) end end end + + class Version3Encryptor < Version1Encryptor + include Chef::EncryptedDataBagItem::Assertions + + def initialize(plaintext_data, key, iv=nil) + super + assert_aead_requirements_met!(algorithm) + @auth_tag = nil + end + + # Returns a wrapped and encrypted version of +plaintext_data+ suitable for + # using as the value in an encrypted data bag item. + def for_encrypted_item + { + "encrypted_data" => encrypted_data, + "iv" => Base64.encode64(iv), + "auth_tag" => Base64.encode64(auth_tag), + "version" => 3, + "cipher" => algorithm + } + end + + # Returns the used encryption algorithm + def algorithm + AEAD_ALGORITHM + end + + # Returns a wrapped and encrypted version of +plaintext_data+ suitable for + # Returns the auth_tag. + def auth_tag + # Generated auth_tag comes from OpenSSL::Cipher#auth_tag + # This must be generated after the data is encrypted + if @auth_tag.nil? + raise EncryptionFailure, "Internal Error: GCM authentication tag read before encryption" + end + @auth_tag + end + + # Generates (and memoizes) an OpenSSL::Cipher object and configures + # it for the specified iv and encryption key using AEAD + def openssl_encryptor + @openssl_encryptor ||= begin + encryptor = super + encryptor.auth_data = '' + encryptor + end + end + + # Encrypts, Base64 encodes +serialized_data+ and gets the authentication tag + def encrypted_data + @encrypted_data ||= begin + enc_data_b64 = super + @auth_tag = openssl_encryptor.auth_tag + enc_data_b64 + end + end + + end + end end diff --git a/lib/chef/environment.rb b/lib/chef/environment.rb index 5c719ca285..33dfb52403 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) @@ -140,7 +140,6 @@ class Chef self end - def update_attributes_from_params(params) unless params[:default_attributes].nil? || params[:default_attributes].size == 0 default_attributes(Chef::JSONCompat.from_json(params[:default_attributes])) @@ -213,7 +212,6 @@ class Chef end end - def self.json_create(o) environment = new environment.name(o["name"]) diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb index 22fafaa4dc..bd6d887884 100644 --- a/lib/chef/exceptions.rb +++ b/lib/chef/exceptions.rb @@ -115,6 +115,7 @@ class Chef class Win32ArchitectureIncorrect < RuntimeError; end class ObsoleteDependencySyntax < ArgumentError; end class InvalidDataBagPath < ArgumentError; end + class DuplicateDataBagItem < RuntimeError; end # A different version of a cookbook was added to a # VersionedRecipeList than the one already there. @@ -194,7 +195,6 @@ class Chef end end - end # Exception class for collecting multiple failures. Used when running # delayed notifications so that chef can process each delayed @@ -262,7 +262,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 @@ -297,7 +297,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 @@ -331,6 +331,18 @@ class Chef end end + class ChecksumMismatch < RuntimeError + def initialize(res_cksum, cont_cksum) + super "Checksum on resource (#{res_cksum}) does not match checksum on content (#{cont_cksum})" + end + 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/handler/json_file.rb b/lib/chef/handler/json_file.rb index 3473db1838..405c91795e 100644 --- a/lib/chef/handler/json_file.rb +++ b/lib/chef/handler/json_file.rb @@ -58,7 +58,6 @@ class Chef end end - end end end diff --git a/lib/chef/json_compat.rb b/lib/chef/json_compat.rb index 2dbb607d9b..ddccfe5fcb 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 'json' require 'ffi_yajl/json_gem' # XXX: parts of chef require JSON gem's Hash#to_json monkeypatch +require 'chef/exceptions' 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 @@ -88,14 +99,21 @@ class Chef end def to_json(obj, opts = nil) - obj.to_json(opts) + begin + FFI_Yajl::Encoder.encode(obj, opts) + rescue FFI_Yajl::EndodeError => 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 @@ -130,7 +148,7 @@ class Chef when /^Chef::Resource/ Chef::Resource.find_subclass_by_name(json_class) else - raise JSON::ParserError, "Unsupported `json_class` type '#{json_class}'" + raise Chef::Exceptions::JSON::ParseError, "Unsupported `json_class` type '#{json_class}'" 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/client_delete.rb b/lib/chef/knife/client_delete.rb index 1902145c8d..d7d302ee1d 100644 --- a/lib/chef/knife/client_delete.rb +++ b/lib/chef/knife/client_delete.rb @@ -47,7 +47,7 @@ class Chef object = Chef::ApiClient.load(@client_name) if object.validator unless config[:delete_validators] - ui.fatal("You must specify --force to delete the validator client #{@client_name}") + ui.fatal("You must specify --delete-validators to delete the validator client #{@client_name}") exit 2 end end diff --git a/lib/chef/knife/cookbook_site_share.rb b/lib/chef/knife/cookbook_site_share.rb index 0a8461937d..71bbb00abc 100644 --- a/lib/chef/knife/cookbook_site_share.rb +++ b/lib/chef/knife/cookbook_site_share.rb @@ -101,7 +101,7 @@ class Chef def do_upload(cookbook_filename, cookbook_category, user_id, user_secret_filename) uri = "http://cookbooks.opscode.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/deps.rb b/lib/chef/knife/deps.rb index b2a39a0725..4b23468962 100644 --- a/lib/chef/knife/deps.rb +++ b/lib/chef/knife/deps.rb @@ -77,7 +77,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) result = [] if node['chef_environment'] && node['chef_environment'] != '_default' result << "/environments/#{node['chef_environment']}.json" @@ -88,7 +88,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) result = [] if role['run_list'] dependencies_from_runlist(role['run_list']).each do |dependency| @@ -138,4 +138,3 @@ class Chef end end end - diff --git a/lib/chef/mixin/shell_out.rb b/lib/chef/mixin/shell_out.rb index 97aa7041fd..3f80290f90 100644 --- a/lib/chef/mixin/shell_out.rb +++ b/lib/chef/mixin/shell_out.rb @@ -33,7 +33,7 @@ class Chef def shell_out(*command_args) cmd = Mixlib::ShellOut.new(*run_command_compatible_options(command_args)) - cmd.live_stream = io_for_live_stream + cmd.live_stream ||= io_for_live_stream cmd.run_command cmd end diff --git a/lib/chef/node.rb b/lib/chef/node.rb index 17ec1d0f0a..327da67b1c 100644 --- a/lib/chef/node.rb +++ b/lib/chef/node.rb @@ -181,7 +181,6 @@ class Chef attributes.override end - def override_attrs attributes.override end @@ -416,7 +415,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/platform/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb index ebcc8c2fe3..d61298e182 100644 --- a/lib/chef/platform/provider_mapping.rb +++ b/lib/chef/platform/provider_mapping.rb @@ -142,11 +142,14 @@ class Chef }, :centos => { :default => { - :service => Chef::Provider::Service::Redhat, + :service => Chef::Provider::Service::Systemd, :cron => Chef::Provider::Cron, :package => Chef::Provider::Package::Yum, :mdadm => Chef::Provider::Mdadm, :ifconfig => Chef::Provider::Ifconfig::Redhat + }, + "< 7" => { + :service => Chef::Provider::Service::Redhat } }, :amazon => { @@ -159,19 +162,25 @@ class Chef }, :scientific => { :default => { - :service => Chef::Provider::Service::Redhat, + :service => Chef::Provider::Service::Systemd, :cron => Chef::Provider::Cron, :package => Chef::Provider::Package::Yum, :mdadm => Chef::Provider::Mdadm + }, + "< 7" => { + :service => Chef::Provider::Service::Redhat } }, :fedora => { :default => { - :service => Chef::Provider::Service::Redhat, + :service => Chef::Provider::Service::Systemd, :cron => Chef::Provider::Cron, :package => Chef::Provider::Package::Yum, :mdadm => Chef::Provider::Mdadm, :ifconfig => Chef::Provider::Ifconfig::Redhat + }, + "< 15" => { + :service => Chef::Provider::Service::Redhat } }, :opensuse => { @@ -196,19 +205,25 @@ class Chef }, :oracle => { :default => { - :service => Chef::Provider::Service::Redhat, + :service => Chef::Provider::Service::Systemd, :cron => Chef::Provider::Cron, :package => Chef::Provider::Package::Yum, :mdadm => Chef::Provider::Mdadm + }, + "< 7" => { + :service => Chef::Provider::Service::Redhat } }, :redhat => { :default => { - :service => Chef::Provider::Service::Redhat, + :service => Chef::Provider::Service::Systemd, :cron => Chef::Provider::Cron, :package => Chef::Provider::Package::Yum, :mdadm => Chef::Provider::Mdadm, :ifconfig => Chef::Provider::Ifconfig::Redhat + }, + "< 7" => { + :service => Chef::Provider::Service::Systemd } }, :ibm_powerkvm => { @@ -229,6 +244,15 @@ class Chef :ifconfig => Chef::Provider::Ifconfig::Redhat } }, + :parallels => { + :default => { + :service => Chef::Provider::Service::Redhat, + :cron => Chef::Provider::Cron, + :package => Chef::Provider::Package::Yum, + :mdadm => Chef::Provider::Mdadm, + :ifconfig => Chef::Provider::Ifconfig::Redhat + } + }, :gentoo => { :default => { :package => Chef::Provider::Package::Portage, @@ -365,7 +389,8 @@ class Chef :mount => Chef::Provider::Mount::Aix, :ifconfig => Chef::Provider::Ifconfig::Aix, :cron => Chef::Provider::Cron::Aix, - :package => Chef::Provider::Package::Aix + :package => Chef::Provider::Package::Aix, + :user => Chef::Provider::User::Aix } }, :exherbo => { @@ -523,6 +548,8 @@ class Chef if platforms.has_key?(args[:platform]) if platforms[args[:platform]].has_key?(:default) platforms[args[:platform]][:default][args[:resource].to_sym] = args[:provider] + elsif args[:platform] == :default + platforms[:default][args[:resource].to_sym] = args[:provider] else platforms[args[:platform]] = { :default => { args[:resource].to_sym => args[:provider] } } end diff --git a/lib/chef/provider/cookbook_file/content.rb b/lib/chef/provider/cookbook_file/content.rb index cb777dd916..9f49ba885c 100644 --- a/lib/chef/provider/cookbook_file/content.rb +++ b/lib/chef/provider/cookbook_file/content.rb @@ -28,7 +28,7 @@ class Chef def file_for_provider cookbook = run_context.cookbook_collection[resource_cookbook] - file_cache_location = cookbook.preferred_filename_on_disk_location(run_context.node, :files, @new_resource.source, @new_resource.path) + file_cache_location = cookbook.preferred_filename_on_disk_location(run_context.node, :files, @new_resource.source) if file_cache_location.nil? nil else diff --git a/lib/chef/provider/deploy.rb b/lib/chef/provider/deploy.rb index 63ee8685df..426e69644e 100644 --- a/lib/chef/provider/deploy.rb +++ b/lib/chef/provider/deploy.rb @@ -21,7 +21,7 @@ require "chef/mixin/from_file" require "chef/monkey_patches/fileutils" require "chef/provider/git" require "chef/provider/subversion" -require 'chef/dsl/recipe' +require "chef/dsl/recipe" class Chef class Provider @@ -31,7 +31,7 @@ class Chef include Chef::Mixin::FromFile include Chef::Mixin::Command - attr_reader :scm_provider, :release_path, :previous_release_path + attr_reader :scm_provider, :release_path, :shared_path, :previous_release_path def initialize(new_resource, run_context) super(new_resource, run_context) @@ -53,6 +53,7 @@ class Chef def load_current_resource @scm_provider.load_current_resource @release_path = @new_resource.deploy_to + "/releases/#{release_slug}" + @shared_path = @new_resource.shared_path end def sudo(command,&block) 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/env.rb b/lib/chef/provider/env.rb index e857d74d68..e91b8bbb97 100644 --- a/lib/chef/provider/env.rb +++ b/lib/chef/provider/env.rb @@ -143,7 +143,7 @@ class Chef def modify_env if @new_resource.delim #e.g. add to PATH - @new_resource.value << @new_resource.delim << @current_resource.value + @new_resource.value(@new_resource.value + @new_resource.delim + @current_resource.value) end create_env end diff --git a/lib/chef/provider/env/windows.rb b/lib/chef/provider/env/windows.rb index 96b5d3871f..f73cb42f7e 100644 --- a/lib/chef/provider/env/windows.rb +++ b/lib/chef/provider/env/windows.rb @@ -51,17 +51,13 @@ class Chef return obj ? obj.variablevalue : nil end - def find_env(environment_variables, key_name) - environment_variables.find do | environment_variable | - environment_variable['name'].downcase == key_name - end - end - def env_obj(key_name) wmi = WmiLite::Wmi.new - environment_variables = wmi.instances_of('Win32_Environment') - existing_variable = find_env(environment_variables, key_name) - existing_variable.nil? ? nil : existing_variable.wmi_ole_object + # Note that by design this query is case insensitive with regard to key_name + environment_variables = wmi.query("select * from Win32_Environment where name = '#{key_name}'") + if environment_variables && environment_variables.length > 0 + environment_variables[0].wmi_ole_object + end end #see: http://msdn.microsoft.com/en-us/library/ms682653%28VS.85%29.aspx diff --git a/lib/chef/provider/file.rb b/lib/chef/provider/file.rb index 031d0fb005..256248f240 100644 --- a/lib/chef/provider/file.rb +++ b/lib/chef/provider/file.rb @@ -56,6 +56,10 @@ class Chef attr_reader :deployment_strategy + attr_accessor :needs_creating + attr_accessor :needs_unlinking + attr_accessor :managing_symlink + def initialize(new_resource, run_context) @content_class ||= Chef::Provider::File::Content if new_resource.respond_to?(:atomic_update) @@ -69,22 +73,47 @@ class Chef end def load_current_resource + # true if there is a symlink and we need to manage what it points at + @managing_symlink = file_class.symlink?(new_resource.path) && ( new_resource.manage_symlink_source || new_resource.manage_symlink_source.nil? ) + + # true if there is a non-file thing in the way that we need to unlink first + @needs_unlinking = + if ::File.exist?(new_resource.path) + if managing_symlink? + !symlink_to_real_file?(new_resource.path) + else + !real_file?(new_resource.path) + end + else + false + end + + # true if we are going to be creating a new file + @needs_creating = !::File.exist?(new_resource.path) || needs_unlinking? + # Let children resources override constructing the @current_resource - @current_resource ||= Chef::Resource::File.new(@new_resource.name) - @current_resource.path(@new_resource.path) - if ::File.exists?(@current_resource.path) && ::File.file?(::File.realpath(@current_resource.path)) + @current_resource ||= Chef::Resource::File.new(new_resource.name) + current_resource.path(new_resource.path) + + if !needs_creating? + # we are updating an existing file if managing_content? - Chef::Log.debug("#{@new_resource} checksumming file at #{@new_resource.path}.") - @current_resource.checksum(checksum(@current_resource.path)) + Chef::Log.debug("#{new_resource} checksumming file at #{new_resource.path}.") + current_resource.checksum(checksum(current_resource.path)) + else + # if the file does not exist or is not a file, then the checksum is invalid/pointless + current_resource.checksum(nil) end - load_resource_attributes_from_file(@current_resource) + load_resource_attributes_from_file(current_resource) end - @current_resource + + current_resource end def define_resource_requirements # deep inside FAC we have to assert requirements, so call FACs hook to set that up access_controls.define_resource_requirements + # Make sure the parent directory exists, otherwise fail. For why-run assume it would have been created. requirements.assert(:create, :create_if_missing, :touch) do |a| parent_directory = ::File.dirname(@new_resource.path) @@ -94,7 +123,7 @@ class Chef end # Make sure the file is deletable if it exists, otherwise fail. - if ::File.exists?(@new_resource.path) + if ::File.exist?(@new_resource.path) requirements.assert(:delete) do |a| a.assertion { ::File.writable?(@new_resource.path) } a.failure_message(Chef::Exceptions::InsufficientPermissions,"File #{@new_resource.path} exists but is not writable so it cannot be deleted") @@ -114,6 +143,8 @@ class Chef end def action_create + do_generate_content + do_validate_content do_unlink do_create_file do_contents_changes @@ -123,10 +154,10 @@ class Chef end def action_create_if_missing - if ::File.exists?(@new_resource.path) - Chef::Log.debug("#{@new_resource} exists at #{@new_resource.path} taking no action.") - else + unless ::File.exist?(@new_resource.path) action_create + else + Chef::Log.debug("#{@new_resource} exists at #{@new_resource.path} taking no action.") end end @@ -275,6 +306,15 @@ class Chef !file_class.symlink?(path) && ::File.file?(path) end + # like real_file? that follows (sane) symlinks + def symlink_to_real_file?(path) + begin + real_file?(::File.realpath(path)) + rescue Errno::ELOOP, Errno::ENOENT + false + end + end + # Similar to File.exist?, but also returns true in the case that the # named file is a broken symlink. def l_exist?(path) @@ -290,41 +330,42 @@ class Chef end end + def do_generate_content + # referencing the tempfile magically causes content to be generated + tempfile + end + + def tempfile_checksum + @tempfile_checksum ||= checksum(tempfile.path) + end + + def do_validate_content + if new_resource.checksum && tempfile && ( new_resource.checksum != tempfile_checksum ) + raise Chef::Exceptions::ChecksumMismatch.new(short_cksum(new_resource.checksum), short_cksum(tempfile_checksum)) + end + end + def do_unlink - @file_unlinked = false if @new_resource.force_unlink - if l_exist?(@new_resource.path) && !real_file?(@new_resource.path) + if needs_unlinking? # unlink things that aren't normal files description = "unlink #{file_type_string(@new_resource.path)} at #{@new_resource.path}" converge_by(description) do unlink(@new_resource.path) end - @current_resource.checksum = nil - @file_unlinked = true end end end - def file_unlinked? - @file_unlinked == true - end - def do_create_file - @file_created = false - if !::File.exists?(@new_resource.path) || file_unlinked? + if needs_creating? converge_by("create new file #{@new_resource.path}") do deployment_strategy.create(@new_resource.path) Chef::Log.info("#{@new_resource} created file #{@new_resource.path}") end - @file_created = true end end - # do_contents_changes needs to know if do_create_file created a file or not - def file_created? - @file_created == true - end - def do_backup(file = nil) Chef::Util::Backup.new(@new_resource, file).backup! end @@ -334,7 +375,7 @@ class Chef end def update_file_contents - do_backup unless file_created? + do_backup unless needs_creating? deployment_strategy.deploy(tempfile.path, ::File.realpath(@new_resource.path)) Chef::Log.info("#{@new_resource} updated file contents #{@new_resource.path}") if managing_content? @@ -353,7 +394,7 @@ class Chef # the file? on the next line suppresses the case in why-run when we have a not-file here that would have otherwise been removed if ::File.file?(@new_resource.path) && contents_changed? description = [ "update content in file #{@new_resource.path} from \ -#{short_cksum(@current_resource.checksum)} to #{short_cksum(checksum(tempfile.path))}" ] +#{short_cksum(@current_resource.checksum)} to #{short_cksum(tempfile_checksum)}" ] # Hide the diff output if the resource is marked as a sensitive resource if @new_resource.sensitive @@ -361,7 +402,7 @@ class Chef description << "suppressed sensitive resource" else diff.diff(@current_resource.path, tempfile.path) - @new_resource.diff( diff.for_reporting ) unless file_created? + @new_resource.diff( diff.for_reporting ) unless needs_creating? description << diff.for_output end @@ -401,11 +442,11 @@ class Chef def contents_changed? Chef::Log.debug "calculating checksum of #{tempfile.path} to compare with #{@current_resource.checksum}" - checksum(tempfile.path) != @current_resource.checksum + tempfile_checksum != @current_resource.checksum end def tempfile - content.tempfile + @tempfile ||= content.tempfile end def short_cksum(checksum) @@ -414,7 +455,6 @@ class Chef end def load_resource_attributes_from_file(resource) - if Chef::Platform.windows? # This is a work around for CHEF-3554. # OC-6534: is tracking the real fix for this workaround. @@ -426,6 +466,17 @@ class Chef acl_scanner.set_all! end + def managing_symlink? + !!@managing_symlink + end + + def needs_creating? + !!@needs_creating + end + + def needs_unlinking? + !!@needs_unlinking + end end end end diff --git a/lib/chef/provider/git.rb b/lib/chef/provider/git.rb index 263014d229..525249a726 100644 --- a/lib/chef/provider/git.rb +++ b/lib/chef/provider/git.rb @@ -150,7 +150,7 @@ class Chef converge_by("checkout ref #{sha_ref} branch #{@new_resource.revision}") do # checkout into a local branch rather than a detached HEAD - shell_out!("git checkout -b #{@new_resource.checkout_branch} #{sha_ref}", run_options(:cwd => @new_resource.destination)) + shell_out!("git checkout -B #{@new_resource.checkout_branch} #{sha_ref}", run_options(:cwd => @new_resource.destination)) Chef::Log.info "#{@new_resource} checked out branch: #{@new_resource.revision} onto: #{@new_resource.checkout_branch} reference: #{sha_ref}" end end diff --git a/lib/chef/provider/group/aix.rb b/lib/chef/provider/group/aix.rb index 9dedef351a..6ac9d03357 100644 --- a/lib/chef/provider/group/aix.rb +++ b/lib/chef/provider/group/aix.rb @@ -16,16 +16,18 @@ # limitations under the License. # -require 'chef/provider/group/usermod' +require 'chef/provider/group/groupadd' +require 'chef/mixin/shell_out' class Chef class Provider class Group - class Aix < Chef::Provider::Group::Usermod + class Aix < Chef::Provider::Group::Groupadd def required_binaries [ "/usr/bin/mkgroup", "/usr/bin/chgroup", + "/usr/bin/chgrpmem", "/usr/sbin/rmgroup" ] end @@ -51,6 +53,19 @@ class Chef run_command(:command => "rmgroup #{@new_resource.group_name}") end + def add_member(member) + shell_out!("chgrpmem -m + #{member} #{@new_resource.group_name}") + end + + def set_members(members) + return if members.empty? + shell_out!("chgrpmem -m = #{members.join(',')} #{@new_resource.group_name}") + end + + def remove_member(member) + shell_out!("chgrpmem -m - #{member} #{@new_resource.group_name}") + end + def set_options opts = "" { :gid => "id" }.sort { |a,b| a[0] <=> b[0] }.each do |field, option| diff --git a/lib/chef/provider/group/dscl.rb b/lib/chef/provider/group/dscl.rb index c204c09321..04ca9bc929 100644 --- a/lib/chef/provider/group/dscl.rb +++ b/lib/chef/provider/group/dscl.rb @@ -39,11 +39,33 @@ class Chef return result[2] end - # This is handled in providers/group.rb by Etc.getgrnam() - # def group_exists?(group) - # groups = safe_dscl("list /Groups") - # !! ( groups =~ Regexp.new("\n#{group}\n") ) - # end + def load_current_resource + @current_resource = Chef::Resource::Group.new(@new_resource.name) + @current_resource.group_name(@new_resource.name) + group_info = nil + begin + group_info = safe_dscl("read /Groups/#{@new_resource.name}") + rescue Chef::Exceptions::Group + @group_exists = false + Chef::Log.debug("#{@new_resource} group does not exist") + end + + if group_info + group_info.each_line do |line| + key, val = line.split(': ') + val.strip! if val + case key.downcase + when 'primarygroupid' + @new_resource.gid(val) unless @new_resource.gid + @current_resource.gid(val) + when 'groupmembership' + @current_resource.members(val.split(' ')) + end + end + end + + @current_resource + end # get a free GID greater than 200 def get_free_gid(search_limit=1000) @@ -115,10 +137,6 @@ class Chef end end - def load_current_resource - super - end - def create_group dscl_create_group set_gid diff --git a/lib/chef/provider/log.rb b/lib/chef/provider/log.rb index 0aee349705..9379ceeefa 100644 --- a/lib/chef/provider/log.rb +++ b/lib/chef/provider/log.rb @@ -25,6 +25,10 @@ class Chef # Chef log provider, allows logging to chef's logs from recipes class ChefLog < Chef::Provider + def whyrun_supported? + true + end + # No concept of a 'current' resource for logs, this is a no-op # # === Return diff --git a/lib/chef/provider/mount/mount.rb b/lib/chef/provider/mount/mount.rb index bb1b796290..207b0cde6c 100644 --- a/lib/chef/provider/mount/mount.rb +++ b/lib/chef/provider/mount/mount.rb @@ -85,7 +85,7 @@ class Chef shell_out!("mount").stdout.each_line do |line| case line - when /^#{device_mount_regex}\s+on\s+#{Regexp.escape(real_mount_point)}/ + when /^#{device_mount_regex}\s+on\s+#{Regexp.escape(real_mount_point)}\s/ mounted = true Chef::Log.debug("Special device #{device_logstring} mounted as #{real_mount_point}") when /^([\/\w])+\son\s#{Regexp.escape(real_mount_point)}\s+/ @@ -167,7 +167,7 @@ class Chef found = false ::File.readlines("/etc/fstab").reverse_each do |line| - if !found && line =~ /^#{device_fstab_regex}\s+#{Regexp.escape(@new_resource.mount_point)}/ + if !found && line =~ /^#{device_fstab_regex}\s+#{Regexp.escape(@new_resource.mount_point)}\s/ found = true Chef::Log.debug("#{@new_resource} is removed from fstab") next diff --git a/lib/chef/provider/package/paludis.rb b/lib/chef/provider/package/paludis.rb index f40962d74d..7c5245fc97 100644 --- a/lib/chef/provider/package/paludis.rb +++ b/lib/chef/provider/package/paludis.rb @@ -34,7 +34,7 @@ class Chef installed = false re = Regexp.new('(.*)[[:blank:]](.*)[[:blank:]](.*)$') - shell_out!("cave -L warning print-ids -m \"*/#{@new_resource.package_name.split('/').last}\" -f \"%c/%p %v %r\n\"").stdout.each_line do |line| + shell_out!("cave -L warning print-ids -M none -m \"*/#{@new_resource.package_name.split('/').last}\" -f \"%c/%p %v %r\n\"").stdout.each_line do |line| res = re.match(line) unless res.nil? case res[3] @@ -59,7 +59,7 @@ class Chef else pkg = "#{@new_resource.package_name}" end - shell_out!("cave -L warning resolve -x#{expand_options(@new_resource.options)} \"#{pkg}\"") + shell_out!("cave -L warning resolve -x#{expand_options(@new_resource.options)} \"#{pkg}\"",:timeout => @new_resource.timeout) end def upgrade_package(name, version) diff --git a/lib/chef/provider/package/rpm.rb b/lib/chef/provider/package/rpm.rb index 9cfd6bb010..bbb561bd15 100644 --- a/lib/chef/provider/package/rpm.rb +++ b/lib/chef/provider/package/rpm.rb @@ -60,7 +60,7 @@ class Chef status = popen4("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}") do |pid, stdin, stdout, stderr| stdout.each do |line| case line - when /([\w\d+_.-]+)\s([\w\d_.-]+)/ + when /^([\w\d+_.-]+)\s([\w\d_.-]+)$/ @current_resource.package_name($1) @new_resource.version($2) @candidate_version = $2 @@ -78,7 +78,7 @@ class Chef @rpm_status = popen4("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@current_resource.package_name}") do |pid, stdin, stdout, stderr| stdout.each do |line| case line - when /([\w\d+_.-]+)\s([\w\d_.-]+)/ + when /^([\w\d+_.-]+)\s([\w\d_.-]+)$/ Chef::Log.debug("#{@new_resource} current version is #{$2}") @current_resource.version($2) end diff --git a/lib/chef/provider/package/zypper.rb b/lib/chef/provider/package/zypper.rb index 2639f18deb..b00bef0f92 100644 --- a/lib/chef/provider/package/zypper.rb +++ b/lib/chef/provider/package/zypper.rb @@ -103,7 +103,7 @@ class Chef private def zypper_package(command, pkgname, version) - version = "=#{version}" unless version.empty? + version = "=#{version}" unless version.nil? || version.empty? if zypper_version < 1.0 shell_out!("zypper#{gpg_checks} #{command} -y #{pkgname}") else diff --git a/lib/chef/provider/remote_file/cache_control_data.rb b/lib/chef/provider/remote_file/cache_control_data.rb index 8331f3d31a..75b2a5535a 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, Chef::Exceptions::JSON::ParseError false end diff --git a/lib/chef/provider/user/aix.rb b/lib/chef/provider/user/aix.rb new file mode 100644 index 0000000000..af08ab4364 --- /dev/null +++ b/lib/chef/provider/user/aix.rb @@ -0,0 +1,95 @@ +# +# Copyright:: Copyright (c) 2012 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +class Chef + class Provider + class User + class Aix < Chef::Provider::User::Useradd + + UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:shell, "-s"], [:uid, "-u"]] + + def create_user + super + add_password + end + + def manage_user + add_password + manage_home + super + end + + # Aix does not support -r like other unix, sytem account is created by adding to 'system' group + def useradd_options + opts = [] + opts << "-g" << "system" if new_resource.system + opts + end + + def check_lock + lock_info = shell_out!("lsuser -a account_locked #{new_resource.username}") + if whyrun_mode? && passwd_s.stdout.empty? && lock_info.stderr.match(/does not exist/) + # if we're in whyrun mode and the user is not yet created we assume it would be + return false + end + raise Chef::Exceptions::User, "Cannot determine if #{@new_resource} is locked!" if lock_info.stdout.empty? + + status = /\S+\s+account_locked=(\S+)/.match(lock_info.stdout) + if status && status[1] == "true" + @locked = true + else + @locked = false + end + + @locked + end + + def lock_user + shell_out!("chuser account_locked=true #{new_resource.username}") + end + + def unlock_user + shell_out!("chuser account_locked=false #{new_resource.username}") + end + + private + def add_password + if @current_resource.password != @new_resource.password && @new_resource.password + Chef::Log.debug("#{@new_resource.username} setting password to #{@new_resource.password}") + command = "echo '#{@new_resource.username}:#{@new_resource.password}' | chpasswd -e" + shell_out!(command) + end + end + + # Aix specific handling to update users home directory. + def manage_home + # -m option does not work on aix, so move dir. + if updating_home? and managing_home_dir? + universal_options.delete("-m") + if ::File.directory?(@current_resource.home) + Chef::Log.debug("Changing users home directory from #{@current_resource.home} to #{new_resource.home}") + shell_out!("mv #{@current_resource.home} #{new_resource.home}") + else + Chef::Log.debug("Creating users home directory #{new_resource.home}") + shell_out!("mkdir -p #{new_resource.home}") + end + end + end + + end + end + end +end diff --git a/lib/chef/provider/user/useradd.rb b/lib/chef/provider/user/useradd.rb index cad9d58e7e..cc770c0be2 100644 --- a/lib/chef/provider/user/useradd.rb +++ b/lib/chef/provider/user/useradd.rb @@ -46,6 +46,7 @@ class Chef def remove_user command = [ "userdel" ] command << "-r" if managing_home_dir? + command << "-f" if new_resource.force command << new_resource.username shell_out!(*command) end diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb index c89037df31..3c9e94e6f7 100644 --- a/lib/chef/providers.rb +++ b/lib/chef/providers.rb @@ -91,6 +91,7 @@ require 'chef/provider/user/pw' require 'chef/provider/user/useradd' require 'chef/provider/user/windows' require 'chef/provider/user/solaris' +require 'chef/provider/user/aix' require 'chef/provider/group/aix' require 'chef/provider/group/dscl' diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index 6c8e0434a0..6adf937f53 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -134,7 +134,6 @@ F extend Chef::Mixin::ConvertToClassName - if Module.method(:const_defined?).arity == 1 def self.strict_const_defined?(const) const_defined?(const) @@ -277,7 +276,6 @@ F end end - def updated=(true_or_false) Chef::Log.warn("Chef::Resource#updated=(true|false) is deprecated. Please call #updated_by_last_action(true|false) instead.") Chef::Log.warn("Called from:") @@ -544,7 +542,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/paludis_package.rb b/lib/chef/resource/paludis_package.rb index e456750b70..fde25e69b3 100644 --- a/lib/chef/resource/paludis_package.rb +++ b/lib/chef/resource/paludis_package.rb @@ -27,6 +27,7 @@ class Chef @resource_name = :paludis_package @provider = Chef::Provider::Package::Paludis @allowed_actions = [ :install, :remove, :upgrade ] + @timeout = 3600 end end end diff --git a/lib/chef/resource/user.rb b/lib/chef/resource/user.rb index 357d6d12ea..05c076229f 100644 --- a/lib/chef/resource/user.rb +++ b/lib/chef/resource/user.rb @@ -38,6 +38,7 @@ class Chef @password = nil @system = false @manage_home = false + @force = false @non_unique = false @action = :create @supports = { @@ -121,6 +122,14 @@ class Chef ) end + def force(arg=nil) + set_or_return( + :force, + arg, + :kind_of => [ TrueClass, FalseClass ] + ) + end + def non_unique(arg=nil) set_or_return( :non_unique, diff --git a/lib/chef/resource_collection.rb b/lib/chef/resource_collection.rb index a528a18aed..2cbd61cb0c 100644 --- a/lib/chef/resource_collection.rb +++ b/lib/chef/resource_collection.rb @@ -158,7 +158,6 @@ class Chef # compat. alias_method :resources, :find - # Returns true if +query_object+ is a valid string for looking up a # resource, or raises InvalidResourceSpecification if not. # === Arguments @@ -189,16 +188,19 @@ class Chef end # Serialize this object as a hash - def to_json(*a) + def to_hash instance_vars = Hash.new self.instance_variables.each do |iv| instance_vars[iv] = self.instance_variable_get(iv) end - results = { + { 'json_class' => self.class.name, 'instance_vars' => instance_vars } - results.to_json(*a) + end + + def to_json(*a) + Chef::JSONCompat.to_json(to_hash, *a) end def self.json_create(o) diff --git a/lib/chef/resource_reporter.rb b/lib/chef/resource_reporter.rb index 47bbd13741..a19f26125e 100644 --- a/lib/chef/resource_reporter.rb +++ b/lib/chef/resource_reporter.rb @@ -230,18 +230,18 @@ 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) begin - 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) @rest_client.raw_http_request(:POST, reporting_url, headers({'Content-Encoding' => 'gzip'}), compressed_data) - rescue Net::HTTPServerException => e - if e.response.code.to_s == "400" + rescue StandardError => e + if e.respond_to? :response Chef::FileCache.store("failed-reporting-data.json", Chef::JSONCompat.to_json_pretty(run_data), 0640) - Chef::Log.error("Failed to post reporting data to server (HTTP 400), saving to #{Chef::FileCache.load("failed-reporting-data.json", false)}") + Chef::Log.error("Failed to post reporting data to server (HTTP #{e.response.code}), saving to #{Chef::FileCache.load("failed-reporting-data.json", false)}") else - Chef::Log.error("Failed to post reporting data to server (HTTP #{e.response.code.to_s})") + Chef::Log.error("Failed to post reporting data to server (#{e})") end end else @@ -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 64952c6ab2..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) @@ -236,8 +236,8 @@ class Chef paths = Array(Chef::Config[:role_path]) paths.each do |path| roles_files = Dir.glob(File.join(path, "**", "**")) - js_files = roles_files.select { |file| file.match /#{name}\.json$/ } - rb_files = roles_files.select { |file| file.match /#{name}\.rb$/ } + js_files = roles_files.select { |file| file.match /\/#{name}\.json$/ } + rb_files = roles_files.select { |file| file.match /\/#{name}\.rb$/ } if js_files.count > 1 or rb_files.count > 1 raise Chef::Exceptions::DuplicateRole, "Multiple roles of same type found named #{name}" end diff --git a/lib/chef/run_list.rb b/lib/chef/run_list.rb index 684c5e19fc..01e32ffc98 100644 --- a/lib/chef/run_list.rb +++ b/lib/chef/run_list.rb @@ -85,8 +85,12 @@ class Chef @run_list_items.join(", ") end - def to_json(*args) - to_a.map { |item| item.to_s}.to_json(*args) + def for_json + to_a.map { |item| item.to_s } + end + + def to_json(*a) + Chef::JSONCompat.to_json(for_json, *a) end def empty? @@ -158,6 +162,5 @@ class Chef end end - end end 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/diff.rb b/lib/chef/util/diff.rb index 7bce52d874..3117484a71 100644 --- a/lib/chef/util/diff.rb +++ b/lib/chef/util/diff.rb @@ -82,7 +82,7 @@ class Chef end end end - + # produces a unified-output-format diff with 3 lines of context # ChefFS uses udiff() directly def udiff(old_file, new_file) @@ -185,4 +185,3 @@ class Chef end end end - diff --git a/lib/chef/version_constraint.rb b/lib/chef/version_constraint.rb index 7bfde41e74..a78e32e94f 100644 --- a/lib/chef/version_constraint.rb +++ b/lib/chef/version_constraint.rb @@ -24,7 +24,7 @@ class Chef PATTERN = /^(#{OPS.join('|')}) *([0-9].*)$/ VERSION_CLASS = Chef::Version - attr_reader :op, :version, :raw_version + attr_reader :op, :version def initialize(constraint_spec=DEFAULT_CONSTRAINT) case constraint_spec @@ -50,11 +50,11 @@ class Chef end def inspect - "(#{@op} #{@version})" + "(#{to_s})" end def to_s - "#{@op} #{@version}" + "#{@op} #{@raw_version}" end def eql?(o) @@ -106,7 +106,7 @@ class Chef @op = $1 @raw_version = $2 @version = self.class::VERSION_CLASS.new(@raw_version) - if raw_version.split('.').size <= 2 + if @raw_version.split('.').size <= 2 @missing_patch_level = true end else |