diff options
author | John Keiser <john@johnkeiser.com> | 2015-11-17 14:40:33 -0800 |
---|---|---|
committer | John Keiser <john@johnkeiser.com> | 2015-12-15 13:01:32 -0800 |
commit | 9dbe7491927ecf73d5757179dc6e26c1946f46c0 (patch) | |
tree | 231566c67d2a72831126468398b37b4b7b20116c /lib | |
parent | b743fcdbd172862386d172b42bbbc31cfe2f4239 (diff) | |
download | chef-9dbe7491927ecf73d5757179dc6e26c1946f46c0.tar.gz |
Add knife/ChefFS support for policyfiles and policy_groups.
Diffstat (limited to 'lib')
34 files changed, 594 insertions, 129 deletions
diff --git a/lib/chef/chef_fs.rb b/lib/chef/chef_fs.rb index bc445e53ad..13cf39c9ec 100644 --- a/lib/chef/chef_fs.rb +++ b/lib/chef/chef_fs.rb @@ -1,5 +1,55 @@ require 'chef/platform' +# +# ChefFS was designed to be a near-1:1 translation between Chef server endpoints +# and local data, so that it could be used for: +# +# 1. User editing, diffing and viewing of server content locally +# 2. knife download, upload and diff (supporting the above scenario) +# 3. chef-client -z (serving user repository directly) +# +# This is the translation between chef-zero data stores (which correspond +# closely to server endpoints) and the ChefFS repository format. +# +# |-----------------------------------|-----------------------------------| +# | chef-zero DataStore | ChefFS (repository) | +# |-----------------------------------|-----------------------------------| +# | <root> | org.json | +# | association_requests/NAME | invitations.json | +# | clients/NAME | clients/NAME.json | +# | cookbooks/NAME/VERSION | cookbooks/NAME/metadata.rb | +# | containers/NAME | containers/NAME.json | +# | data/BAG/ITEM | data_bags/BAG/ITEM.json | +# | environments/NAME | environments/NAME.json | +# | groups/NAME | groups/NAME.json | +# | nodes/NAME | nodes/NAME.json | +# | policies/NAME/REVISION | policies/NAME-REVISION.json | +# | policy_groups/NAME/policies/PNAME | policy_groups/NAME.json | +# | roles/NAME | roles/NAME.json | +# | sandboxes/ID | <not stored on disk, just memory> | +# | users/NAME | members.json | +# | file_store/COOKBOOK/VERSION/PATH | cookbooks/COOKBOOK/PATH | +# | **/_acl | acls/**.json | +# |-----------------------------------|-----------------------------------| +# +# +# ## The Code +# +# There are two main entry points to ChefFS: +# +# - ChefServerRootDir represents the chef server (under an org) and surfaces a +# filesystem-like interface (FSBaseObject / FSBaseDir) that maps the REST API +# to the same format as you would have on disk. +# - ChefRepositoryFileSystemRootDir represents the local repository where you +# put your cookbooks, roles, policies, etc. +# +# Because these two map to a common directory structure, diff, upload, download, +# and other filesystem operations, can easily be done in a generic manner. +# +# These are instantiated by Chef::ChefFS::Config's `chef_fs` and `local_fs` +# methods. +# + class Chef module ChefFS def self.windows? diff --git a/lib/chef/chef_fs/chef_fs_data_store.rb b/lib/chef/chef_fs/chef_fs_data_store.rb index 4d07135a0a..fef8b1df77 100644 --- a/lib/chef/chef_fs/chef_fs_data_store.rb +++ b/lib/chef/chef_fs/chef_fs_data_store.rb @@ -241,6 +241,22 @@ class Chef raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e) end + # /policy_groups/NAME/policies/POLICYNAME: return the revision of the given policy + elsif path[0] == 'policy_groups' && path[2] == 'policies' && path.length == 4 + with_entry(path[0..1]) do |entry| + policy_group = Chef::JSONCompat.parse(entry) + if !policy_group['policies'] || !policy_group['policies'][path[3]] || !policy_group['policies'][path[3]] + raise ChefZero::DataStore::DataNotFoundError.new(path, entry) + end + # The policy group looks like: + # { + # "policies": { + # "x": { "revision_id": "10" } + # } + # } + Chef::JSONCompat.to_json_pretty(policy_group['policies'][path[3]]["revision_id"]) + end + # GET [/organizations/ORG]/users/NAME -> /users/NAME # Manipulates members.json elsif is_org? && path[0] == 'users' && path.length == 2 @@ -382,6 +398,17 @@ class Chef if use_memory_store?(path) @memory_store.list(path) + elsif path[0] == 'policy_groups' && path.length == 2 + with_entry(path) do |entry| + [ 'policies' ] + end + + elsif path[0] == 'policy_groups' && path[2] == 'policies' && path.length == 3 + with_entry([ 'policy_groups', path[1] ]) do |entry| + policy_group = Chef::JSONCompat.parse(entry.read) + (policy_group['policies'] || {}).keys + end + elsif path[0] == 'cookbooks' && path.length == 1 with_entry(path) do |entry| begin @@ -444,6 +471,12 @@ class Chef @memory_store.exists_dir?(path) elsif path[0] == 'cookbooks' && path.length == 2 list([ path[0] ]).include?(path[1]) + # /policy_groups/NAME/policies + elsif path[0] == 'policy_groups' && path[2] == 'policies' && path.length == 3 + exists_dir?(path[0..1]) + # /policy_groups/NAME/policies/POLICYNAME + elsif path[0] == 'policy_groups' && path[2] == 'policies' && path.length == 4 + exists_dir?(path[0..1]) && list(path[0..2]).include?(path[3]) else Chef::ChefFS::FileSystem.resolve_path(chef_fs, to_chef_fs_path(path)).exists? end diff --git a/lib/chef/chef_fs/config.rb b/lib/chef/chef_fs/config.rb index 40cbb36530..a666731b80 100644 --- a/lib/chef/chef_fs/config.rb +++ b/lib/chef/chef_fs/config.rb @@ -40,7 +40,8 @@ class Chef "nodes" => "node", "roles" => "role", "users" => "user", - "policies" => "policy" + "policies" => "policy", + "policy_groups" => "policy_group" } INFLECTIONS.each { |k,v| k.freeze; v.freeze } INFLECTIONS.freeze @@ -232,11 +233,11 @@ class Chef result = {} case @chef_config[:repo_mode] when 'static' - object_names = %w(cookbooks data_bags environments roles policies) + object_names = %w(cookbooks data_bags environments roles) when 'hosted_everything' - object_names = %w(acls clients cookbooks containers data_bags environments groups nodes roles policies) + object_names = %w(acls clients cookbooks containers data_bags environments groups nodes roles policies policy_groups) else - object_names = %w(clients cookbooks data_bags environments nodes roles users policies) + object_names = %w(clients cookbooks data_bags environments nodes roles users) end object_names.each do |object_name| # cookbooks -> cookbook_path diff --git a/lib/chef/chef_fs/data_handler/policy_data_handler.rb b/lib/chef/chef_fs/data_handler/policy_data_handler.rb index 769c13c364..9f4d7479fc 100644 --- a/lib/chef/chef_fs/data_handler/policy_data_handler.rb +++ b/lib/chef/chef_fs/data_handler/policy_data_handler.rb @@ -4,12 +4,39 @@ class Chef module ChefFS module DataHandler class PolicyDataHandler < DataHandlerBase + def name_and_revision(name) + # foo-1.0.0 = foo, 1.0.0 + name = remove_dot_json(name) + if name =~ /^(.*)-([^-]*)$/ + name, revision_id = $1, $2 + end + revision_id ||= '0.0.0' + [ name, revision_id ] + end def normalize(policy, entry) - policy + # foo-1.0.0 = foo, 1.0.0 + name, revision_id = name_and_revision(entry.name) + defaults = { + 'name' => name, + 'revision_id' => revision_id, + 'run_list' => [], + 'cookbook_locks' => {} + } + normalize_hash(policy, defaults) + end + + def verify_integrity(object_data, entry, &on_error) + name, revision = name_and_revision(entry.name) + if object_data['name'] != name + on_error.call("Object name '#{object_data['name']}' doesn't match entry '#{entry.name}'.") + end + + if object_data['revision_id'] != revision + on_error.call("Object revision ID '#{object_data['revision']}' doesn't match entry '#{entry.name}'.") + end end end end end end - diff --git a/lib/chef/chef_fs/data_handler/policy_group_data_handler.rb b/lib/chef/chef_fs/data_handler/policy_group_data_handler.rb new file mode 100644 index 0000000000..6ada606e49 --- /dev/null +++ b/lib/chef/chef_fs/data_handler/policy_group_data_handler.rb @@ -0,0 +1,27 @@ +require 'chef/chef_fs/data_handler/data_handler_base' + +class Chef + module ChefFS + module DataHandler + class PolicyGroupDataHandler < DataHandlerBase + + def normalize(policy_group, entry) + defaults = { + "name" => remove_dot_json(entry.name), + "policies" => {} + } + result = normalize_hash(policy_group, defaults) + result.delete("uri") # not useful data + result + end + + def verify_integrity(object_data, entry, &on_error) + if object_data["policies"].empty? + on_error.call("Policy group #{object_data["name"]} does not have any policies in it.") + end + end + + end + end + end +end diff --git a/lib/chef/chef_fs/file_system/acl_dir.rb b/lib/chef/chef_fs/file_system/acl_dir.rb index 9f68d7cda7..66cf794319 100644 --- a/lib/chef/chef_fs/file_system/acl_dir.rb +++ b/lib/chef/chef_fs/file_system/acl_dir.rb @@ -47,7 +47,7 @@ class Chef end def create_child(name, file_contents) - raise OperationNotAllowedError.new(:create_child, self), "ACLs can only be updated, and can only be created when the corresponding object is created." + raise OperationNotAllowedError.new(:create_child, self, nil, "ACLs can only be updated, and can only be created when the corresponding object is created.") end def data_handler diff --git a/lib/chef/chef_fs/file_system/acl_entry.rb b/lib/chef/chef_fs/file_system/acl_entry.rb index b2545af5ae..e739bd8f10 100644 --- a/lib/chef/chef_fs/file_system/acl_entry.rb +++ b/lib/chef/chef_fs/file_system/acl_entry.rb @@ -32,7 +32,7 @@ class Chef end def delete(recurse) - raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self), "ACLs cannot be deleted." + raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self, nil, "ACLs cannot be deleted") end def write(file_contents) @@ -42,12 +42,12 @@ class Chef begin rest.put("#{api_path}/#{permission}", { permission => acls[permission] }) rescue Timeout::Error => e - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "Timeout writing: #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "Timeout writing: #{e}") rescue Net::HTTPServerException => e if e.response.code == "404" raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e) else - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "HTTP error writing: #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "HTTP error writing: #{e}") end end end diff --git a/lib/chef/chef_fs/file_system/acls_dir.rb b/lib/chef/chef_fs/file_system/acls_dir.rb index a8c63726b7..d8c9bf809c 100644 --- a/lib/chef/chef_fs/file_system/acls_dir.rb +++ b/lib/chef/chef_fs/file_system/acls_dir.rb @@ -28,10 +28,6 @@ class Chef class AclsDir < BaseFSDir ENTITY_TYPES = %w(clients containers cookbooks data_bags environments groups nodes roles) # we don't read sandboxes, so we don't read their acls - def initialize(parent) - super('acls', parent) - end - def data_handler @data_handler ||= Chef::ChefFS::DataHandler::AclDataHandler.new end diff --git a/lib/chef/chef_fs/file_system/already_exists_error.rb b/lib/chef/chef_fs/file_system/already_exists_error.rb index bf8994fdf3..e6d4a8fa2c 100644 --- a/lib/chef/chef_fs/file_system/already_exists_error.rb +++ b/lib/chef/chef_fs/file_system/already_exists_error.rb @@ -22,9 +22,6 @@ class Chef module ChefFS module FileSystem class AlreadyExistsError < OperationFailedError - def initialize(operation, entry, cause = nil) - super(operation, entry, cause) - end end end end diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb index 267fe30456..6a59ccff85 100644 --- a/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb +++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb @@ -21,11 +21,12 @@ require 'chef/chef_fs/file_system/chef_repository_file_system_entry' require 'chef/chef_fs/file_system/chef_repository_file_system_acls_dir' require 'chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir' require 'chef/chef_fs/file_system/chef_repository_file_system_data_bags_dir' -require 'chef/chef_fs/file_system/chef_repository_file_system_policies_dir' require 'chef/chef_fs/file_system/multiplexed_dir' require 'chef/chef_fs/data_handler/client_data_handler' require 'chef/chef_fs/data_handler/environment_data_handler' require 'chef/chef_fs/data_handler/node_data_handler' +require 'chef/chef_fs/data_handler/policy_data_handler' +require 'chef/chef_fs/data_handler/policy_group_data_handler' require 'chef/chef_fs/data_handler/role_data_handler' require 'chef/chef_fs/data_handler/user_data_handler' require 'chef/chef_fs/data_handler/group_data_handler' @@ -163,8 +164,6 @@ class Chef dirs = paths.map { |path| ChefRepositoryFileSystemCookbooksDir.new(name, self, path) } when 'data_bags' dirs = paths.map { |path| ChefRepositoryFileSystemDataBagsDir.new(name, self, path) } - when 'policies' - dirs = paths.map { |path| ChefRepositoryFileSystemPoliciesDir.new(name, self, path) } when 'acls' dirs = paths.map { |path| ChefRepositoryFileSystemAclsDir.new(name, self, path) } else @@ -175,6 +174,10 @@ class Chef Chef::ChefFS::DataHandler::EnvironmentDataHandler.new when 'nodes' Chef::ChefFS::DataHandler::NodeDataHandler.new + when 'policies' + Chef::ChefFS::DataHandler::PolicyDataHandler.new + when 'policy_groups' + Chef::ChefFS::DataHandler::PolicyGroupDataHandler.new when 'roles' Chef::ChefFS::DataHandler::RoleDataHandler.new when 'users' diff --git a/lib/chef/chef_fs/file_system/chef_server_root_dir.rb b/lib/chef/chef_fs/file_system/chef_server_root_dir.rb index a243e0ae6b..09181ac4b4 100644 --- a/lib/chef/chef_fs/file_system/chef_server_root_dir.rb +++ b/lib/chef/chef_fs/file_system/chef_server_root_dir.rb @@ -26,12 +26,18 @@ require 'chef/chef_fs/file_system/nodes_dir' require 'chef/chef_fs/file_system/org_entry' require 'chef/chef_fs/file_system/organization_invites_entry' require 'chef/chef_fs/file_system/organization_members_entry' +require 'chef/chef_fs/file_system/policies_dir' +require 'chef/chef_fs/file_system/policy_groups_dir' require 'chef/chef_fs/file_system/environments_dir' +require 'chef/chef_fs/data_handler/acl_data_handler' require 'chef/chef_fs/data_handler/client_data_handler' +require 'chef/chef_fs/data_handler/environment_data_handler' +require 'chef/chef_fs/data_handler/node_data_handler' require 'chef/chef_fs/data_handler/role_data_handler' require 'chef/chef_fs/data_handler/user_data_handler' require 'chef/chef_fs/data_handler/group_data_handler' require 'chef/chef_fs/data_handler/container_data_handler' +require 'chef/chef_fs/data_handler/policy_group_data_handler' class Chef module ChefFS @@ -133,26 +139,45 @@ class Chef def children @children ||= begin result = [ - CookbooksDir.new(self), - DataBagsDir.new(self), - EnvironmentsDir.new(self), + # /cookbooks + CookbooksDir.new("cookbooks", self), + # /data_bags + DataBagsDir.new("data_bags", self, "data"), + # /environments + EnvironmentsDir.new("environments", self, nil, Chef::ChefFS::DataHandler::EnvironmentDataHandler.new), + # /roles RestListDir.new("roles", self, nil, Chef::ChefFS::DataHandler::RoleDataHandler.new) ] if repo_mode == 'hosted_everything' result += [ - AclsDir.new(self), + # /acls + AclsDir.new("acls", self), + # /clients RestListDir.new("clients", self, nil, Chef::ChefFS::DataHandler::ClientDataHandler.new), + # /containers RestListDir.new("containers", self, nil, Chef::ChefFS::DataHandler::ContainerDataHandler.new), + # /groups RestListDir.new("groups", self, nil, Chef::ChefFS::DataHandler::GroupDataHandler.new), - NodesDir.new(self), + # /nodes + NodesDir.new("nodes", self, nil, Chef::ChefFS::DataHandler::NodeDataHandler.new), + # /org.json OrgEntry.new("org.json", self), + # /members.json OrganizationMembersEntry.new("members.json", self), - OrganizationInvitesEntry.new("invitations.json", self) + # /invitations.json + OrganizationInvitesEntry.new("invitations.json", self), + # /policies + PoliciesDir.new("policies", self, nil, Chef::ChefFS::DataHandler::PolicyDataHandler.new), + # /policy_groups + PolicyGroupsDir.new("policy_groups", self, nil, Chef::ChefFS::DataHandler::PolicyGroupDataHandler.new), ] elsif repo_mode != 'static' result += [ + # /clients RestListDir.new("clients", self, nil, Chef::ChefFS::DataHandler::ClientDataHandler.new), - NodesDir.new(self), + # /nodes + NodesDir.new("nodes", self, nil, Chef::ChefFS::DataHandler::NodeDataHandler.new), + # /users RestListDir.new("users", self, nil, Chef::ChefFS::DataHandler::UserDataHandler.new) ] end diff --git a/lib/chef/chef_fs/file_system/cookbook_dir.rb b/lib/chef/chef_fs/file_system/cookbook_dir.rb index c0f0390e98..42408c0869 100644 --- a/lib/chef/chef_fs/file_system/cookbook_dir.rb +++ b/lib/chef/chef_fs/file_system/cookbook_dir.rb @@ -128,17 +128,17 @@ class Chef begin rest.delete(api_path) rescue Timeout::Error => e - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e), "Timeout deleting: #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "Timeout deleting: #{e}") rescue Net::HTTPServerException if $!.response.code == "404" raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!) else - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e), "HTTP error deleting: #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "HTTP error deleting: #{e}") end end else raise NotFoundError.new(self) if !exists? - raise MustDeleteRecursivelyError.new(self), "#{path_for_printing} must be deleted recursively" + raise MustDeleteRecursivelyError.new(self, "#{path_for_printing} must be deleted recursively") end end @@ -197,14 +197,14 @@ class Chef end rescue Timeout::Error => e - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "Timeout reading: #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "Timeout reading: #{e}") rescue Net::HTTPServerException => e if e.response.code == "404" @could_not_get_chef_object = e raise Chef::ChefFS::FileSystem::NotFoundError.new(self, @could_not_get_chef_object) else - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "HTTP error reading: #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "HTTP error reading: #{e}") end # Chef bug http://tickets.opscode.com/browse/CHEF-3066 ... instead of 404 we get 500 right now. @@ -214,7 +214,7 @@ class Chef @could_not_get_chef_object = e raise Chef::ChefFS::FileSystem::NotFoundError.new(self, @could_not_get_chef_object) else - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "HTTP error reading: #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "HTTP error reading: #{e}") end end end diff --git a/lib/chef/chef_fs/file_system/cookbook_file.rb b/lib/chef/chef_fs/file_system/cookbook_file.rb index 16203b727c..f755174183 100644 --- a/lib/chef/chef_fs/file_system/cookbook_file.rb +++ b/lib/chef/chef_fs/file_system/cookbook_file.rb @@ -39,9 +39,9 @@ class Chef begin tmpfile = rest.streaming_request(file[:url]) rescue Timeout::Error => e - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "Timeout reading #{file[:url]}: #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "Timeout reading #{file[:url]}: #{e}") rescue Net::HTTPServerException => e - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "#{e.message} retrieving #{file[:url]}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "#{e.message} retrieving #{file[:url]}") end begin diff --git a/lib/chef/chef_fs/file_system/cookbook_frozen_error.rb b/lib/chef/chef_fs/file_system/cookbook_frozen_error.rb index 705673384d..cbb25cd03a 100644 --- a/lib/chef/chef_fs/file_system/cookbook_frozen_error.rb +++ b/lib/chef/chef_fs/file_system/cookbook_frozen_error.rb @@ -22,9 +22,6 @@ class Chef module ChefFS module FileSystem class CookbookFrozenError < AlreadyExistsError - def initialize(operation, entry, cause = nil) - super(operation, entry, cause) - end end end end diff --git a/lib/chef/chef_fs/file_system/cookbooks_dir.rb b/lib/chef/chef_fs/file_system/cookbooks_dir.rb index 6f49c28996..8cbc06931b 100644 --- a/lib/chef/chef_fs/file_system/cookbooks_dir.rb +++ b/lib/chef/chef_fs/file_system/cookbooks_dir.rb @@ -32,10 +32,6 @@ class Chef include Chef::Mixin::FileClass - def initialize(parent) - super("cookbooks", parent) - end - def make_child_entry(name) result = @children.select { |child| child.name == name }.first if @children result || CookbookDir.new(name, self) @@ -65,16 +61,16 @@ class Chef def upload_cookbook_from(other, options = {}) root.versioned_cookbooks ? upload_versioned_cookbook(other, options) : upload_unversioned_cookbook(other, options) rescue Timeout::Error => e - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "Timeout writing: #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "Timeout writing: #{e}") rescue Net::HTTPServerException => e case e.response.code when "409" - raise Chef::ChefFS::FileSystem::CookbookFrozenError.new(:write, self, e), "Cookbook #{other.name} is frozen" + raise Chef::ChefFS::FileSystem::CookbookFrozenError.new(:write, self, e, "Cookbook #{other.name} is frozen") else - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "HTTP error writing: #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "HTTP error writing: #{e}") end rescue Chef::Exceptions::CookbookFrozen => e - raise Chef::ChefFS::FileSystem::CookbookFrozenError.new(:write, self, e), "Cookbook #{other.name} is frozen" + raise Chef::ChefFS::FileSystem::CookbookFrozenError.new(:write, self, e, "Cookbook #{other.name} is frozen") end # Knife currently does not understand versioned cookbooks diff --git a/lib/chef/chef_fs/file_system/data_bag_dir.rb b/lib/chef/chef_fs/file_system/data_bag_dir.rb index 212f76fdb9..7a4463e69d 100644 --- a/lib/chef/chef_fs/file_system/data_bag_dir.rb +++ b/lib/chef/chef_fs/file_system/data_bag_dir.rb @@ -49,17 +49,17 @@ class Chef def delete(recurse) if !recurse raise NotFoundError.new(self) if !exists? - raise MustDeleteRecursivelyError.new(self), "#{path_for_printing} must be deleted recursively" + raise MustDeleteRecursivelyError.new(self, "#{path_for_printing} must be deleted recursively") end begin rest.delete(api_path) rescue Timeout::Error => e - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e), "Timeout deleting: #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "Timeout deleting: #{e}") rescue Net::HTTPServerException => e if e.response.code == "404" raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e) else - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e), "HTTP error deleting: #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "HTTP error deleting: #{e}") end end end diff --git a/lib/chef/chef_fs/file_system/data_bags_dir.rb b/lib/chef/chef_fs/file_system/data_bags_dir.rb index 1cb61bbd1a..0645a6c16f 100644 --- a/lib/chef/chef_fs/file_system/data_bags_dir.rb +++ b/lib/chef/chef_fs/file_system/data_bags_dir.rb @@ -23,10 +23,6 @@ class Chef module ChefFS module FileSystem class DataBagsDir < RestListDir - def initialize(parent) - super("data_bags", parent, "data") - end - def make_child_entry(name, exists = false) result = @children.select { |child| child.name == name }.first if @children result || DataBagDir.new(name, self, exists) @@ -36,12 +32,12 @@ class Chef begin @children ||= root.get_json(api_path).keys.sort.map { |entry| make_child_entry(entry, true) } rescue Timeout::Error => e - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "Timeout getting children: #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "Timeout getting children: #{e}") rescue Net::HTTPServerException => e if e.response.code == "404" raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e) else - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "HTTP error getting children: #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error getting children: #{e}") end end end @@ -54,12 +50,12 @@ class Chef begin rest.post(api_path, { 'name' => name }) rescue Timeout::Error => e - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e), "Timeout creating child '#{name}': #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Timeout creating child '#{name}': #{e}") rescue Net::HTTPServerException => e if e.response.code == "409" - raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e), "Cannot create #{name} under #{path}: already exists" + raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e, "Cannot create #{name} under #{path}: already exists") else - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e), "HTTP error creating child '#{name}': #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "HTTP error creating child '#{name}': #{e}") end end @children = nil diff --git a/lib/chef/chef_fs/file_system/default_environment_cannot_be_modified_error.rb b/lib/chef/chef_fs/file_system/default_environment_cannot_be_modified_error.rb index 8ca3b917ca..a077474da8 100644 --- a/lib/chef/chef_fs/file_system/default_environment_cannot_be_modified_error.rb +++ b/lib/chef/chef_fs/file_system/default_environment_cannot_be_modified_error.rb @@ -22,10 +22,6 @@ class Chef module ChefFS module FileSystem class DefaultEnvironmentCannotBeModifiedError < OperationNotAllowedError - def initialize(operation, entry, cause = nil) - super(operation, entry, cause) - end - def reason result = super result + " (default environment cannot be modified)" diff --git a/lib/chef/chef_fs/file_system/environments_dir.rb b/lib/chef/chef_fs/file_system/environments_dir.rb index 3aee3ee5af..984129bab3 100644 --- a/lib/chef/chef_fs/file_system/environments_dir.rb +++ b/lib/chef/chef_fs/file_system/environments_dir.rb @@ -20,16 +20,11 @@ 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/chef_fs/file_system/default_environment_cannot_be_modified_error' -require 'chef/chef_fs/data_handler/environment_data_handler' class Chef module ChefFS module FileSystem class EnvironmentsDir < RestListDir - def initialize(parent) - super("environments", parent, nil, Chef::ChefFS::DataHandler::EnvironmentDataHandler.new) - end - def make_child_entry(name, exists = nil) if name == '_default.json' DefaultEnvironmentEntry.new(name, self, exists) @@ -46,12 +41,12 @@ class Chef def delete(recurse) raise NotFoundError.new(self) if !exists? - raise DefaultEnvironmentCannotBeModifiedError.new(:delete, self), "#{path_for_printing} cannot be deleted." + raise DefaultEnvironmentCannotBeModifiedError.new(:delete, self) end def write(file_contents) raise NotFoundError.new(self) if !exists? - raise DefaultEnvironmentCannotBeModifiedError.new(:write, self), "#{path_for_printing} cannot be updated." + raise DefaultEnvironmentCannotBeModifiedError.new(:write, self) end end end diff --git a/lib/chef/chef_fs/file_system/file_system_error.rb b/lib/chef/chef_fs/file_system/file_system_error.rb index 80aff35893..6a5cedec34 100644 --- a/lib/chef/chef_fs/file_system/file_system_error.rb +++ b/lib/chef/chef_fs/file_system/file_system_error.rb @@ -20,13 +20,24 @@ class Chef module ChefFS module FileSystem class FileSystemError < StandardError - def initialize(entry, cause = nil) + # @param entry The entry which had an issue. + # @param cause The wrapped exception (if any). + # @param reason A string describing why this exception happened. + def initialize(entry, cause = nil, reason = nil) + super(reason) @entry = entry @cause = cause + @reason = reason end + # The entry which had an issue. attr_reader :entry + + # The wrapped exception (if any). attr_reader :cause + + # A string describing why this exception happened. + attr_reader :reason end end end diff --git a/lib/chef/chef_fs/file_system/must_delete_recursively_error.rb b/lib/chef/chef_fs/file_system/must_delete_recursively_error.rb index bfa8ba28ce..11b26e514e 100644 --- a/lib/chef/chef_fs/file_system/must_delete_recursively_error.rb +++ b/lib/chef/chef_fs/file_system/must_delete_recursively_error.rb @@ -22,9 +22,6 @@ class Chef module ChefFS module FileSystem class MustDeleteRecursivelyError < FileSystemError - def initialize(entry, cause = nil) - super(entry, cause) - end end end end diff --git a/lib/chef/chef_fs/file_system/nodes_dir.rb b/lib/chef/chef_fs/file_system/nodes_dir.rb index 2610b06a82..12bc1c06c8 100644 --- a/lib/chef/chef_fs/file_system/nodes_dir.rb +++ b/lib/chef/chef_fs/file_system/nodes_dir.rb @@ -25,10 +25,6 @@ class Chef module ChefFS module FileSystem class NodesDir < RestListDir - def initialize(parent) - super("nodes", parent, nil, Chef::ChefFS::DataHandler::NodeDataHandler.new) - end - # Identical to RestListDir.children, except supports environments def children begin @@ -36,12 +32,12 @@ class Chef make_child_entry("#{key}.json", true) end rescue Timeout::Error => e - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "Timeout retrieving children: #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "Timeout retrieving children: #{e}") rescue Net::HTTPServerException => e if $!.response.code == "404" raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!) else - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "HTTP error retrieving children: #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}") end end end diff --git a/lib/chef/chef_fs/file_system/not_found_error.rb b/lib/chef/chef_fs/file_system/not_found_error.rb index 9eab3d6131..088c38cc64 100644 --- a/lib/chef/chef_fs/file_system/not_found_error.rb +++ b/lib/chef/chef_fs/file_system/not_found_error.rb @@ -22,9 +22,6 @@ class Chef module ChefFS module FileSystem class NotFoundError < FileSystemError - def initialize(entry, cause = nil) - super(entry, cause) - end end end end diff --git a/lib/chef/chef_fs/file_system/operation_failed_error.rb b/lib/chef/chef_fs/file_system/operation_failed_error.rb index 28d170d628..70461e082e 100644 --- a/lib/chef/chef_fs/file_system/operation_failed_error.rb +++ b/lib/chef/chef_fs/file_system/operation_failed_error.rb @@ -22,8 +22,8 @@ class Chef module ChefFS module FileSystem class OperationFailedError < FileSystemError - def initialize(operation, entry, cause = nil) - super(entry, cause) + def initialize(operation, entry, cause = nil, reason = nil) + super(entry, cause, reason) @operation = operation end diff --git a/lib/chef/chef_fs/file_system/operation_not_allowed_error.rb b/lib/chef/chef_fs/file_system/operation_not_allowed_error.rb index 4b4f9742a8..e756cc76a3 100644 --- a/lib/chef/chef_fs/file_system/operation_not_allowed_error.rb +++ b/lib/chef/chef_fs/file_system/operation_not_allowed_error.rb @@ -22,26 +22,24 @@ class Chef module ChefFS module FileSystem class OperationNotAllowedError < FileSystemError - def initialize(operation, entry, cause = nil) - super(entry, cause) + def initialize(operation, entry, cause = nil, reason = nil) + reason ||= + case operation + when :delete + "cannot be deleted" + when :write + "cannot be updated" + when :create_child + "cannot have a child created under it" + when :read + "cannot be read" + end + super(entry, cause, reason) @operation = operation end attr_reader :operation attr_reader :entry - - def reason - case operation - when :delete - "cannot be deleted" - when :write - "cannot be updated" - when :create_child - "cannot have a child created under it" - when :read - "cannot be read" - end - end end end end diff --git a/lib/chef/chef_fs/file_system/org_entry.rb b/lib/chef/chef_fs/file_system/org_entry.rb index 852956e1e5..df3acba528 100644 --- a/lib/chef/chef_fs/file_system/org_entry.rb +++ b/lib/chef/chef_fs/file_system/org_entry.rb @@ -7,11 +7,6 @@ class Chef # /organizations/NAME/org.json # Represents the actual data at /organizations/NAME (the full name, etc.) class OrgEntry < RestListEntry - def initialize(name, parent, exists = nil) - super(name, parent) - @exists = exists - end - def data_handler Chef::ChefFS::DataHandler::OrganizationDataHandler.new end diff --git a/lib/chef/chef_fs/file_system/policies_dir.rb b/lib/chef/chef_fs/file_system/policies_dir.rb new file mode 100644 index 0000000000..9edaf7cf0a --- /dev/null +++ b/lib/chef/chef_fs/file_system/policies_dir.rb @@ -0,0 +1,145 @@ +# +# Author:: John Keiser (<jkeiser@opscode.com>) +# 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. +# + +require 'chef/chef_fs/file_system/rest_list_dir' +require 'chef/chef_fs/file_system/policy_revision_entry' + +class Chef + module ChefFS + module FileSystem + # + # Server API: + # /policies - list of policies by name + # - /policies/NAME - represents a policy with all revisions + # - /policies/NAME/revisions - list of revisions for that policy + # - /policies/NAME/revisions/REVISION - actual policy-revision document + # + # Local Repository and ChefFS: + # /policies - PoliciesDir - maps to server API /policies + # - /policies/NAME-REVISION.json - PolicyRevision - maps to /policies/NAME/revisions/REVISION + # + class PoliciesDir < RestListDir + # Children: NAME-REVISION.json for all revisions of all policies + # + # /nodes: { + # "node1": "https://api.opscode.com/organizations/myorg/nodes/node1", + # "node2": "https://api.opscode.com/organizations/myorg/nodes/node2", + # } + # + # /policies: { + # "foo": {} + # } + + def make_child_entry(name, exists = nil) + @children.select { |child| child.name == name }.first if @children + PolicyRevisionEntry.new(name, self, exists) + end + + # Children come from /policies in this format: + # { + # "foo": { + # "uri": "https://api.opscode.com/organizations/essentials/policies/foo", + # "revisions": { + # "1.0.0": { + # + # }, + # "1.0.1": { + # + # } + # } + # } + # } + def children + begin + # Grab the names of the children, append json, and make child entries + @children ||= begin + result = [] + data = root.get_json(api_path) + data.keys.sort.each do |policy_name| + data[policy_name]["revisions"].keys.each do |policy_revision| + filename = "#{policy_name}-#{policy_revision}.json" + result << make_child_entry(filename, true) + end + end + result + end + rescue Timeout::Error => e + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "Timeout retrieving children: #{e}") + rescue Net::HTTPServerException => e + # 404 = NotFoundError + if $!.response.code == "404" + raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!) + # Anything else is unexpected (OperationFailedError) + else + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}") + end + end + end + + # + # Does POST <api_path> with file_contents + # + def create_child(name, file_contents) + # Parse the contents to ensure they are valid JSON + begin + 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 + + # Create the child entry that will be returned + result = make_child_entry(name, true) + + # Normalize the file_contents before post (add defaults, etc.) + if data_handler + object = data_handler.normalize_for_post(object, result) + data_handler.verify_integrity(object, result) do |error| + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, nil, "Error creating '#{name}': #{error}") + end + end + + # POST /api_path with the normalized file_contents + begin + policy_name, policy_revision = data_handler.name_and_revision(name) + rest.post("#{api_path}/#{policy_name}/revisions", object) + rescue Timeout::Error => e + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Timeout creating '#{name}': #{e}") + rescue Net::HTTPServerException => e + # 404 = NotFoundError + if e.response.code == "404" + raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e) + # 409 = AlreadyExistsError + elsif $!.response.code == "409" + raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e, "Failure creating '#{name}': #{path}/#{name} already exists") + # Anything else is unexpected (OperationFailedError) + else + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Failure creating '#{name}': #{e.message}") + end + end + + # Clear the cache of children so that if someone asks for children + # again, we will get it again + @children = nil + + result + end + + end + end + end +end diff --git a/lib/chef/chef_fs/file_system/policy_group_entry.rb b/lib/chef/chef_fs/file_system/policy_group_entry.rb new file mode 100644 index 0000000000..9885310cc7 --- /dev/null +++ b/lib/chef/chef_fs/file_system/policy_group_entry.rb @@ -0,0 +1,135 @@ +# +# Author:: John Keiser (<jkeiser@opscode.com>) +# 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. +# + +require 'chef/chef_fs/file_system/already_exists_error' +require 'chef/chef_fs/file_system/not_found_error' +require 'chef/chef_fs/file_system/operation_failed_error' + +class Chef + module ChefFS + module FileSystem + # Represents an entire policy group. + # Server path: /organizations/ORG/policy_groups/GROUP + # Repository path: policy_groups\GROUP.json + # Format: + # { + # "policies": { + # "a": { "revision_id": "1.0.0" } + # } + # } + class PolicyGroupEntry < RestListEntry + # delete is handled normally: + # DELETE /organizations/ORG/policy_groups/GROUP + + # read is handled normally: + # GET /organizations/ORG/policy_groups/GROUP + + # write is different. + # For each policy: + # - PUT /organizations/ORG/policy_groups/GROUP/policies/POLICY + # For each policy on the server but not the client: + # - DELETE /organizations/ORG/policy_groups/GROUP/policies/POLICY + # If the server has associations for a, b and c, + # And the client wants associations for a, x and y, + # We must PUT a, x and y + # And DELETE b and c + def write(file_contents) + # Parse the contents to ensure they are valid JSON + begin + 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 + + if data_handler + object = data_handler.normalize_for_put(object, self) + data_handler.verify_integrity(object, self) do |error| + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, nil, "Error creating '#{name}': #{error}") + end + end + + begin + + # this should all get carted to PolicyGroupEntry#write. + + # the server demands the full policy data, but we want users' local policy_group documents to just + # have the data you'd get from GET /policy_groups/POLICY_GROUP. so we try to fetch that. + + # ordinarily this would be POST to the normal URL, but we do PUT to + # /organizations/{organization}/policy_groups/{policy_group}/policies/{policy_name} with the full + # policy data, for each individual policy. + policy_datas = {} + + object["policies"].each do |policy_name, policy_data| + policy_path = "/policies/#{policy_name}/revisions/#{policy_data["revision_id"]}" + + get_data = begin + rest.get(policy_path) + rescue Net::HTTPServerException => e + raise "Could not find policy '#{policy_name}'' with revision '#{policy_data["revision_id"]}'' on the server" + end + + # GET policy data + server_policy_data = Chef::JSONCompat.parse(get_data) + + # if it comes back 404, raise an Exception with "Policy file X does not exist with revision Y on the server" + + # otherwise, add it to the list of policyfile datas. + policy_datas[policy_name] = server_policy_data + end + + begin + existing_group = Chef::JSONCompat.parse(self.read) + rescue NotFoundError + # It's OK if the group doesn't already exist, just means no existing policies + end + + # now we have the fullpolicy data for each policies, which is what the PUT endpoint demands. + policy_datas.each do |policy_name, policy_data| + # PUT /organizations/ORG/policy_groups/GROUP/policies/NAME + rest.put("#{api_path}/policies/#{policy_name}", policy_data) + end + + # Now we need to remove any policies that are *not* in our current group. + if existing_group && existing_group['policies'] + (existing_group['policies'].keys - policy_datas.keys).each do |policy_name| + rest.delete("#{api_path}/policies/#{policy_name}") + end + end + + rescue Timeout::Error => e + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Timeout creating '#{name}': #{e}") + rescue Net::HTTPServerException => e + # 404 = NotFoundError + if e.response.code == "404" + raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e) + # 409 = AlreadyExistsError + elsif $!.response.code == "409" + raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e, "Failure creating '#{name}': #{path}/#{name} already exists") + # Anything else is unexpected (OperationFailedError) + else + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Failure creating '#{name}': #{e.message}") + end + end + + self + end + end + end + end +end diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_policies_dir.rb b/lib/chef/chef_fs/file_system/policy_groups_dir.rb index 42768f10b7..c98ae50b97 100644 --- a/lib/chef/chef_fs/file_system/chef_repository_file_system_policies_dir.rb +++ b/lib/chef/chef_fs/file_system/policy_groups_dir.rb @@ -16,23 +16,26 @@ # limitations under the License. # -require 'chef/chef_fs/file_system/chef_repository_file_system_entry' -require 'chef/chef_fs/data_handler/policy_data_handler' +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/chef_fs/file_system/policy_group_entry' class Chef module ChefFS module FileSystem - - class ChefRepositoryFileSystemPoliciesDir < ChefRepositoryFileSystemEntry - def initialize(name, parent, path = nil) - super(name, parent, path, Chef::ChefFS::DataHandler::PolicyDataHandler.new) + class PolicyGroupsDir < RestListDir + def make_child_entry(name, exists = nil) + PolicyGroupEntry.new(name, self, exists) end - def can_have_child?(name, is_dir) - is_dir && !name.start_with?('.') + def create_child(name, file_contents) + entry = make_child_entry(name, true) + entry.write(file_contents) + @children = nil + entry end end end end end - diff --git a/lib/chef/chef_fs/file_system/policy_revision_entry.rb b/lib/chef/chef_fs/file_system/policy_revision_entry.rb new file mode 100644 index 0000000000..896586c54f --- /dev/null +++ b/lib/chef/chef_fs/file_system/policy_revision_entry.rb @@ -0,0 +1,23 @@ +require 'chef/chef_fs/file_system/rest_list_entry' +require 'chef/chef_fs/data_handler/policy_data_handler' + +class Chef + module ChefFS + module FileSystem + # /policies/NAME-REVISION.json + # Represents the actual data at /organizations/ORG/policies/NAME/revisions/REVISION + class PolicyRevisionEntry < RestListEntry + + # /policies/foo-1.0.0.json -> /policies/foo/revisions/1.0.0 + def api_path(options={}) + policy_name, revision_id = data_handler.name_and_revision(name) + "#{parent.api_path}/#{policy_name}/revisions/#{revision_id}" + end + + def write(file_contents) + raise OperationNotAllowedError.new(:write, self, nil, "cannot be updated: policy revisions are immutable once uploaded. If you want to change the policy, create a new revision with your changes") + end + end + end + end +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 0ac735a2c4..4c520aa71a 100644 --- a/lib/chef/chef_fs/file_system/rest_list_dir.rb +++ b/lib/chef/chef_fs/file_system/rest_list_dir.rb @@ -37,52 +37,77 @@ class Chef name =~ /\.json$/ && !is_dir end + # + # Does GET /<api_path>, assumes the result is of the format: + # + # { + # "foo": "<api_path>/foo", + # "bar": "<api_path>/bar", + # } + # + # Children are foo.json and bar.json in this case. + # def children begin + # Grab the names of the children, append json, and make child entries @children ||= root.get_json(api_path).keys.sort.map do |key| make_child_entry("#{key}.json", true) end rescue Timeout::Error => e - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "Timeout retrieving children: #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "Timeout retrieving children: #{e}") rescue Net::HTTPServerException => e + # 404 = NotFoundError if $!.response.code == "404" raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!) + # Anything else is unexpected (OperationFailedError) else - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "HTTP error retrieving children: #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}") end end end + # + # Does POST <api_path> with file_contents + # def create_child(name, file_contents) + # Parse the contents to ensure they are valid JSON begin 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}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Parse error reading JSON creating child '#{name}': #{e}") end + # Create the child entry that will be returned result = make_child_entry(name, true) + # Normalize the file_contents before post (add defaults, etc.) if data_handler object = data_handler.normalize_for_post(object, result) data_handler.verify_integrity(object, result) do |error| - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self), "Error creating '#{name}': #{error}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, nil, "Error creating '#{name}': #{error}") end end + # POST /api_path with the normalized file_contents begin rest.post(api_path, object) rescue Timeout::Error => e - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e), "Timeout creating '#{name}': #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Timeout creating '#{name}': #{e}") rescue Net::HTTPServerException => e + # 404 = NotFoundError if e.response.code == "404" raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e) + # 409 = AlreadyExistsError elsif $!.response.code == "409" - raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e), "Failure creating '#{name}': #{path}/#{name} already exists" + raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e, "Failure creating '#{name}': #{path}/#{name} already exists") + # Anything else is unexpected (OperationFailedError) else - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e), "Failure creating '#{name}': #{e.message}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Failure creating '#{name}': #{e.message}") end end + # Clear the cache of children so that if someone asks for children + # again, we will get it again @children = nil result 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 f68794cb0d..49c6cc9e67 100644 --- a/lib/chef/chef_fs/file_system/rest_list_entry.rb +++ b/lib/chef/chef_fs/file_system/rest_list_entry.rb @@ -70,12 +70,12 @@ class Chef begin rest.delete(api_path) rescue Timeout::Error => e - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e), "Timeout deleting: #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "Timeout deleting: #{e}") rescue Net::HTTPServerException => e if e.response.code == "404" raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e) else - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e), "Timeout deleting: #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "Timeout deleting: #{e}") end end end @@ -89,12 +89,12 @@ class Chef # Minimize the value (get rid of defaults) so the results don't look terrible root.get_json(api_path) rescue Timeout::Error => e - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "Timeout reading: #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "Timeout reading: #{e}") rescue Net::HTTPServerException => e if $!.response.code == "404" raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e) else - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "HTTP error reading: #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "HTTP error reading: #{e}") end end end @@ -148,25 +148,25 @@ class Chef begin 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}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "Parse error reading JSON: #{e}") end if data_handler object = data_handler.normalize_for_put(object, self) data_handler.verify_integrity(object, self) do |error| - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self), "#{error}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, nil, "#{error}") end end begin rest.put(api_path, object) rescue Timeout::Error => e - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "Timeout writing: #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "Timeout writing: #{e}") rescue Net::HTTPServerException => e if e.response.code == "404" raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e) else - raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "HTTP error writing: #{e}" + raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "HTTP error writing: #{e}") end end end diff --git a/lib/chef/chef_fs/parallelizer/parallel_enumerable.rb b/lib/chef/chef_fs/parallelizer/parallel_enumerable.rb index 8845b6926a..26dd30592c 100644 --- a/lib/chef/chef_fs/parallelizer/parallel_enumerable.rb +++ b/lib/chef/chef_fs/parallelizer/parallel_enumerable.rb @@ -266,7 +266,7 @@ class Chef begin output = @block.call(input) @unconsumed_output.push([ output, index, input, :result ]) - rescue + rescue StandardError, ScriptError if @options[:stop_on_exception] @unconsumed_input.clear end diff --git a/lib/chef/knife/list.rb b/lib/chef/knife/list.rb index 137d61f3a5..263b0e7431 100644 --- a/lib/chef/knife/list.rb +++ b/lib/chef/knife/list.rb @@ -47,6 +47,7 @@ class Chef args = pattern_args_from(patterns) all_results = parallelize(pattern_args_from(patterns)) do |pattern| pattern_results = Chef::ChefFS::FileSystem.list(config[:local] ? local_fs : chef_fs, pattern).to_a + if pattern_results.first && !pattern_results.first.exists? && pattern.exact_path ui.error "#{format_path(pattern_results.first)}: No such file or directory" self.exit_code = 1 |