summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/chef/chef_fs.rb50
-rw-r--r--lib/chef/chef_fs/chef_fs_data_store.rb33
-rw-r--r--lib/chef/chef_fs/config.rb9
-rw-r--r--lib/chef/chef_fs/data_handler/policy_data_handler.rb31
-rw-r--r--lib/chef/chef_fs/data_handler/policy_group_data_handler.rb27
-rw-r--r--lib/chef/chef_fs/file_system/acl_dir.rb2
-rw-r--r--lib/chef/chef_fs/file_system/acl_entry.rb6
-rw-r--r--lib/chef/chef_fs/file_system/acls_dir.rb4
-rw-r--r--lib/chef/chef_fs/file_system/already_exists_error.rb3
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb9
-rw-r--r--lib/chef/chef_fs/file_system/chef_server_root_dir.rb39
-rw-r--r--lib/chef/chef_fs/file_system/cookbook_dir.rb12
-rw-r--r--lib/chef/chef_fs/file_system/cookbook_file.rb4
-rw-r--r--lib/chef/chef_fs/file_system/cookbook_frozen_error.rb3
-rw-r--r--lib/chef/chef_fs/file_system/cookbooks_dir.rb12
-rw-r--r--lib/chef/chef_fs/file_system/data_bag_dir.rb6
-rw-r--r--lib/chef/chef_fs/file_system/data_bags_dir.rb14
-rw-r--r--lib/chef/chef_fs/file_system/default_environment_cannot_be_modified_error.rb4
-rw-r--r--lib/chef/chef_fs/file_system/environments_dir.rb9
-rw-r--r--lib/chef/chef_fs/file_system/file_system_error.rb13
-rw-r--r--lib/chef/chef_fs/file_system/must_delete_recursively_error.rb3
-rw-r--r--lib/chef/chef_fs/file_system/nodes_dir.rb8
-rw-r--r--lib/chef/chef_fs/file_system/not_found_error.rb3
-rw-r--r--lib/chef/chef_fs/file_system/operation_failed_error.rb4
-rw-r--r--lib/chef/chef_fs/file_system/operation_not_allowed_error.rb28
-rw-r--r--lib/chef/chef_fs/file_system/org_entry.rb5
-rw-r--r--lib/chef/chef_fs/file_system/policies_dir.rb145
-rw-r--r--lib/chef/chef_fs/file_system/policy_group_entry.rb135
-rw-r--r--lib/chef/chef_fs/file_system/policy_groups_dir.rb (renamed from lib/chef/chef_fs/file_system/chef_repository_file_system_policies_dir.rb)21
-rw-r--r--lib/chef/chef_fs/file_system/policy_revision_entry.rb23
-rw-r--r--lib/chef/chef_fs/file_system/rest_list_dir.rb39
-rw-r--r--lib/chef/chef_fs/file_system/rest_list_entry.rb16
-rw-r--r--lib/chef/chef_fs/parallelizer/parallel_enumerable.rb2
-rw-r--r--lib/chef/knife/list.rb1
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