summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Keiser <john@johnkeiser.com>2015-11-17 14:40:33 -0800
committerJohn Keiser <john@johnkeiser.com>2015-12-15 13:01:32 -0800
commit9dbe7491927ecf73d5757179dc6e26c1946f46c0 (patch)
tree231566c67d2a72831126468398b37b4b7b20116c
parentb743fcdbd172862386d172b42bbbc31cfe2f4239 (diff)
downloadchef-9dbe7491927ecf73d5757179dc6e26c1946f46c0.tar.gz
Add knife/ChefFS support for policyfiles and policy_groups.
-rw-r--r--.gitignore1
-rw-r--r--Gemfile2
-rw-r--r--chef-config/lib/chef-config/config.rb8
-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
-rw-r--r--spec/integration/knife/delete_spec.rb16
-rw-r--r--spec/integration/knife/download_spec.rb155
-rw-r--r--spec/integration/knife/list_spec.rb213
-rw-r--r--spec/integration/knife/upload_spec.rb101
-rw-r--r--spec/unit/chef_fs/config_spec.rb4
42 files changed, 1032 insertions, 191 deletions
diff --git a/.gitignore b/.gitignore
index b5fa1a24d7..cdb29d0679 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@ pkg
tags
*/tags
*~
+.chef
# you should check in your Gemfile.lock in applications, and not in gems
external_tests/*.lock
diff --git a/Gemfile b/Gemfile
index 822a6a8217..d40d3c7161 100644
--- a/Gemfile
+++ b/Gemfile
@@ -5,6 +5,8 @@ gem "activesupport", "< 4.0.0", :group => :compat_testing, :platform => "ruby"
gem 'chef-config', path: "chef-config" if File.exists?(__FILE__ + '../chef-config')
+gem 'chef-zero', github: 'chef/chef-zero'
+
group(:docgen) do
gem "yard"
end
diff --git a/chef-config/lib/chef-config/config.rb b/chef-config/lib/chef-config/config.rb
index d8cc4bff37..1ad6266d14 100644
--- a/chef-config/lib/chef-config/config.rb
+++ b/chef-config/lib/chef-config/config.rb
@@ -185,6 +185,14 @@ module ChefConfig
# Defaults to <chef_repo_path>/nodes.
default(:node_path) { derive_path_from_chef_repo_path('nodes') }
+ # Location of policies on disk. String or array of strings.
+ # Defaults to <chef_repo_path>/policies.
+ default(:policy_path) { derive_path_from_chef_repo_path('policies') }
+
+ # Location of policy_groups on disk. String or array of strings.
+ # Defaults to <chef_repo_path>/policy_groups.
+ default(:policy_group_path) { derive_path_from_chef_repo_path('policy_groups') }
+
# Location of roles on disk. String or array of strings.
# Defaults to <chef_repo_path>/roles.
default(:role_path) { derive_path_from_chef_repo_path('roles') }
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
diff --git a/spec/integration/knife/delete_spec.rb b/spec/integration/knife/delete_spec.rb
index 733a7ef72b..384f06cf83 100644
--- a/spec/integration/knife/delete_spec.rb
+++ b/spec/integration/knife/delete_spec.rb
@@ -967,6 +967,8 @@ EOM
organization 'foo' do
container 'x', {}
group 'x', {}
+ policy 'x', '1.2.3', {}
+ policy_group 'x', { 'policies' => { 'x' => { 'revision_id' => '1.2.3' } } }
end
end
@@ -975,7 +977,7 @@ EOM
end
it 'knife delete /acls/containers/environments.json fails with a reasonable error' do
- knife('delete /acls/containers/environments.json').should_fail "ERROR: /acls/containers/environments.json (remote) cannot be deleted.\n"
+ knife('delete /acls/containers/environments.json').should_fail "ERROR: /acls/containers/environments.json (remote) ACLs cannot be deleted.\n"
end
it 'knife delete /containers/x.json succeeds' do
@@ -988,6 +990,18 @@ EOM
knife('raw /groups/x.json').should_fail(/404/)
end
+ it 'knife delete /policies/x-1.2.3.json succeeds' do
+ knife('raw /policies/x/revisions/1.2.3').should_succeed "{\n \"name\": \"x\",\n \"revision_id\": \"1.2.3\",\n \"run_list\": [\n\n ],\n \"cookbook_locks\": {\n\n }\n}\n"
+ knife('delete /policies/x-1.2.3.json').should_succeed "Deleted /policies/x-1.2.3.json\n"
+ knife('raw /policies/x/revisions/1.2.3').should_fail(/404/)
+ end
+
+ it 'knife delete /policy_groups/x.json succeeds' do
+ knife('raw /policy_groups/x').should_succeed "{\n \"uri\": \"http://127.0.0.1:8900/organizations/foo/policy_groups/x\",\n \"policies\": {\n \"x\": {\n \"revision_id\": \"1.2.3\"\n }\n }\n}\n"
+ knife('delete /policy_groups/x.json').should_succeed "Deleted /policy_groups/x.json\n"
+ knife('raw /policy_groups/x').should_fail(/404/)
+ end
+
it 'knife delete /org.json fails with a reasonable error' do
knife('delete /org.json').should_fail "ERROR: /org.json (remote) cannot be deleted.\n"
end
diff --git a/spec/integration/knife/download_spec.rb b/spec/integration/knife/download_spec.rb
index b8a19061b7..0c334fc65c 100644
--- a/spec/integration/knife/download_spec.rb
+++ b/spec/integration/knife/download_spec.rb
@@ -1094,27 +1094,18 @@ EOM
when_the_chef_server "is in Enterprise mode", :osc_compat => false, :single_org => false do
before do
- organization 'foo' do
- container 'x', {}
- group 'x', {}
- end
+ user 'foo', {}
+ user 'bar', {}
+ user 'foobar', {}
+ organization 'foo', { 'full_name' => 'Something' }
end
before :each do
Chef::Config.chef_server_url = URI.join(Chef::Config.chef_server_url, '/organizations/foo')
end
- when_the_repository 'has existing top level files' do
- before do
- file 'invitations.json', {}
- end
- it "can still download top level files" do
- knife('download /invitations.json').should_succeed
- end
- end
-
- when_the_repository 'is empty' do
- it 'knife download / downloads everything' do
+ when_the_repository "has all the default stuff" do
+ before do
knife('download /').should_succeed <<EOM
Created /acls
Created /acls/clients
@@ -1131,7 +1122,6 @@ Created /acls/containers/nodes.json
Created /acls/containers/policies.json
Created /acls/containers/roles.json
Created /acls/containers/sandboxes.json
-Created /acls/containers/x.json
Created /acls/cookbooks
Created /acls/data_bags
Created /acls/environments
@@ -1141,7 +1131,6 @@ Created /acls/groups/admins.json
Created /acls/groups/billing-admins.json
Created /acls/groups/clients.json
Created /acls/groups/users.json
-Created /acls/groups/x.json
Created /acls/nodes
Created /acls/roles
Created /acls/organization.json
@@ -1159,7 +1148,6 @@ Created /containers/nodes.json
Created /containers/policies.json
Created /containers/roles.json
Created /containers/sandboxes.json
-Created /containers/x.json
Created /cookbooks
Created /data_bags
Created /environments
@@ -1169,14 +1157,141 @@ Created /groups/admins.json
Created /groups/billing-admins.json
Created /groups/clients.json
Created /groups/users.json
-Created /groups/x.json
Created /invitations.json
Created /members.json
Created /nodes
Created /org.json
+Created /policies
+Created /policy_groups
Created /roles
EOM
- knife('diff --name-status /').should_succeed ''
+ end
+
+ context 'and the server has one of each thing' do
+ before do
+ # acl_for %w(organizations foo groups blah)
+ client 'x', {}
+ cookbook 'x', '1.0.0'
+ container 'x', {}
+ data_bag 'x', { 'y' => {} }
+ environment 'x', {}
+ group 'x', {}
+ org_invite 'foo'
+ org_member 'bar'
+ node 'x', {}
+ policy 'x', '1.0.0', {}
+ policy 'blah', '1.0.0', {}
+ policy_group 'x', {
+ 'policies' => {
+ 'x' => { 'revision_id' => '1.0.0' },
+ 'blah' => { 'revision_id' => '1.0.0' }
+ }
+ }
+ role 'x', {}
+ end
+
+ before do
+ knife('download /acls /groups/clients.json /groups/users.json').should_succeed <<-EOM
+Created /acls/clients/x.json
+Created /acls/containers/x.json
+Created /acls/cookbooks/x.json
+Created /acls/data_bags/x.json
+Created /acls/environments/x.json
+Created /acls/groups/x.json
+Created /acls/nodes/x.json
+Created /acls/roles/x.json
+Updated /groups/clients.json
+Updated /groups/users.json
+EOM
+ end
+
+ it 'knife download / downloads everything' do
+ knife('download /').should_succeed <<EOM
+Created /clients/x.json
+Created /containers/x.json
+Created /cookbooks/x
+Created /cookbooks/x/metadata.rb
+Created /data_bags/x
+Created /data_bags/x/y.json
+Created /environments/x.json
+Created /groups/x.json
+Updated /invitations.json
+Updated /members.json
+Created /nodes/x.json
+Created /policies/blah-1.0.0.json
+Created /policies/x-1.0.0.json
+Created /policy_groups/x.json
+Created /roles/x.json
+EOM
+ knife('diff --name-status /').should_succeed ''
+ end
+
+ context "and the repository has an identical copy of each thing" do
+ before do
+ # TODO We have to upload acls for an existing group due to a lack of
+ # dependency detection during upload. Fix that!
+ file 'clients/x.json', { 'public_key' => ChefZero::PUBLIC_KEY }
+ file 'containers/x.json', {}
+ file 'cookbooks/x/metadata.rb', cb_metadata("x", "1.0.0")
+ file 'data_bags/x/y.json', {}
+ file 'environments/x.json', {}
+ file 'groups/x.json', {}
+ file 'invitations.json', [ 'foo' ]
+ file 'members.json', [ 'bar' ]
+ file 'nodes/x.json', {}
+ file 'org.json', { 'full_name' => 'Something' }
+ file 'policies/x-1.0.0.json', { }
+ file 'policies/blah-1.0.0.json', { }
+ file 'policy_groups/x.json', { 'policies' => { 'x' => { 'revision_id' => '1.0.0' }, 'blah' => { 'revision_id' => '1.0.0' } } }
+ file 'roles/x.json', {}
+ end
+
+ it 'knife download makes no changes' do
+ knife('download /').should_succeed ''
+ end
+ end
+
+ context "and the repository has a slightly different copy of each thing" do
+ before do
+ # acl_for %w(organizations foo groups blah)
+ file 'clients/x.json', { 'validator' => true }
+ file 'containers/x.json', {}
+ file 'cookbooks/x/metadata.rb', cb_metadata("x", "1.0.1")
+ file 'data_bags/x/y.json', { 'a' => 'b' }
+ file 'environments/x.json', { 'description' => 'foo' }
+ file 'groups/x.json', { 'description' => 'foo' }
+ file 'groups/x.json', { 'groups' => [ 'admin' ] }
+ file 'nodes/x.json', { 'run_list' => [ 'blah' ] }
+ file 'org.json', { 'full_name' => 'Something Else ' }
+ file 'policies/x-1.0.0.json', { 'run_list' => [ 'blah' ] }
+ file 'policy_groups/x.json', {
+ 'policies' => {
+ 'x' => { 'revision_id' => '1.0.1' },
+ 'y' => { 'revision_id' => '1.0.0' }
+ }
+ }
+ file 'roles/x.json', { 'run_list' => [ 'blah' ] }
+ end
+
+ it 'knife download updates everything' do
+ knife('download /').should_succeed <<EOM
+Updated /clients/x.json
+Updated /cookbooks/x/metadata.rb
+Updated /data_bags/x/y.json
+Updated /environments/x.json
+Updated /groups/x.json
+Updated /invitations.json
+Updated /members.json
+Updated /nodes/x.json
+Updated /org.json
+Created /policies/blah-1.0.0.json
+Updated /policies/x-1.0.0.json
+Updated /policy_groups/x.json
+Updated /roles/x.json
+EOM
+ knife('diff --name-status /').should_succeed ''
+ end
+ end
end
end
end
diff --git a/spec/integration/knife/list_spec.rb b/spec/integration/knife/list_spec.rb
index b289642c7d..eb2ed279e5 100644
--- a/spec/integration/knife/list_spec.rb
+++ b/spec/integration/knife/list_spec.rb
@@ -27,7 +27,7 @@ describe 'knife list', :workstation do
when_the_chef_server "is empty" do
it "knife list / returns all top level directories" do
- knife('list /').should_succeed <<EOM
+ knife('list /').should_succeed <<-EOM
/clients
/cookbooks
/data_bags
@@ -39,7 +39,7 @@ EOM
end
it "knife list -R / returns everything" do
- knife('list -R /').should_succeed <<EOM
+ knife('list -R /').should_succeed <<-EOM
/:
clients
cookbooks
@@ -82,6 +82,9 @@ EOM
environment 'environment2', {}
node 'node1', {}
node 'node2', {}
+ policy 'policy1', '1.2.3', {}
+ policy 'policy2', '1.2.3', {}
+ policy 'policy2', '1.3.5', {}
role 'role1', {}
role 'role2', {}
user 'user1', {}
@@ -89,7 +92,7 @@ EOM
end
it "knife list / returns all top level directories" do
- knife('list /').should_succeed <<EOM
+ knife('list /').should_succeed <<-EOM
/clients
/cookbooks
/data_bags
@@ -101,7 +104,7 @@ EOM
end
it "knife list -R / returns everything" do
- knife('list -R /').should_succeed <<EOM
+ knife('list -R /').should_succeed <<-EOM
/:
clients
cookbooks
@@ -164,7 +167,7 @@ EOM
end
it "knife list -R --flat / returns everything" do
- knife('list -R --flat /').should_succeed <<EOM
+ knife('list -R --flat /').should_succeed <<-EOM
/clients
/clients/chef-validator.json
/clients/chef-webui.json
@@ -202,7 +205,7 @@ EOM
end
it "knife list -Rfp / returns everything" do
- knife('list -Rfp /').should_succeed <<EOM
+ knife('list -Rfp /').should_succeed <<-EOM
/clients/
/clients/chef-validator.json
/clients/chef-webui.json
@@ -240,7 +243,7 @@ EOM
end
it "knife list /cookbooks returns the list of cookbooks" do
- knife('list /cookbooks').should_succeed <<EOM
+ knife('list /cookbooks').should_succeed <<-EOM
/cookbooks/cookbook1
/cookbooks/cookbook2
EOM
@@ -251,7 +254,7 @@ EOM
end
it "knife list /**.rb returns all ruby files" do
- knife('list /**.rb').should_succeed <<EOM
+ knife('list /**.rb').should_succeed <<-EOM
/cookbooks/cookbook1/metadata.rb
/cookbooks/cookbook2/metadata.rb
/cookbooks/cookbook2/recipes/default.rb
@@ -259,7 +262,7 @@ EOM
end
it "knife list /cookbooks/**.rb returns all ruby files" do
- knife('list /cookbooks/**.rb').should_succeed <<EOM
+ knife('list /cookbooks/**.rb').should_succeed <<-EOM
/cookbooks/cookbook1/metadata.rb
/cookbooks/cookbook2/metadata.rb
/cookbooks/cookbook2/recipes/default.rb
@@ -267,7 +270,7 @@ EOM
end
it "knife list /**.json returns all json files" do
- knife('list /**.json').should_succeed <<EOM
+ knife('list /**.json').should_succeed <<-EOM
/clients/chef-validator.json
/clients/chef-webui.json
/clients/client1.json
@@ -290,7 +293,7 @@ EOM
end
it "knife list /data**.json returns all data bag json files" do
- knife('list /data**.json').should_succeed <<EOM
+ knife('list /data**.json').should_succeed <<-EOM
/data_bags/bag1/item1.json
/data_bags/bag1/item2.json
/data_bags/bag2/item1.json
@@ -322,7 +325,7 @@ EOM
before { cwd '.' }
it "knife list -Rfp returns everything" do
- knife('list -Rfp').should_succeed <<EOM
+ knife('list -Rfp').should_succeed <<-EOM
clients/
clients/chef-validator.json
clients/chef-webui.json
@@ -367,7 +370,7 @@ EOM
before { cwd 'cookbooks' }
it "knife list -Rfp / returns everything" do
- knife('list -Rfp /').should_succeed <<EOM
+ knife('list -Rfp /').should_succeed <<-EOM
/clients/
/clients/chef-validator.json
/clients/chef-webui.json
@@ -405,7 +408,7 @@ EOM
end
it "knife list -Rfp .. returns everything" do
- knife('list -Rfp ..').should_succeed <<EOM
+ knife('list -Rfp ..').should_succeed <<-EOM
/clients/
/clients/chef-validator.json
/clients/chef-webui.json
@@ -443,7 +446,7 @@ EOM
end
it "knife list -Rfp returns cookbooks" do
- knife('list -Rfp').should_succeed <<EOM
+ knife('list -Rfp').should_succeed <<-EOM
cookbook1/
cookbook1/metadata.rb
cookbook2/
@@ -462,7 +465,7 @@ EOM
before { cwd 'cookbooks/cookbook2' }
it "knife list -Rfp returns cookbooks" do
- knife('list -Rfp').should_succeed <<EOM
+ knife('list -Rfp').should_succeed <<-EOM
metadata.rb
recipes/
recipes/default.rb
@@ -481,7 +484,7 @@ EOM
before { cwd 'cookbooks' }
it "knife list -Rfp returns cookbooks" do
- knife('list -Rfp').should_succeed <<EOM
+ knife('list -Rfp').should_succeed <<-EOM
cookbook1/
cookbook1/metadata.rb
cookbook2/
@@ -496,7 +499,7 @@ EOM
before { cwd 'symlinked' }
it "knife list -Rfp returns cookbooks" do
- knife('list -Rfp').should_succeed <<EOM
+ knife('list -Rfp').should_succeed <<-EOM
cookbook1/
cookbook1/metadata.rb
cookbook2/
@@ -518,7 +521,7 @@ EOM
before { cwd 'real_cookbooks' }
it "knife list -Rfp returns cookbooks" do
- knife('list -Rfp').should_succeed <<EOM
+ knife('list -Rfp').should_succeed <<-EOM
cookbook1/
cookbook1/metadata.rb
cookbook2/
@@ -533,7 +536,7 @@ EOM
before { cwd 'cookbooks' }
it "knife list -Rfp returns cookbooks" do
- knife('list -Rfp').should_succeed <<EOM
+ knife('list -Rfp').should_succeed <<-EOM
cookbook1/
cookbook1/metadata.rb
cookbook2/
@@ -586,6 +589,7 @@ EOM
file 'environments/environment2.json', {}
file 'nodes/node1.json', {}
file 'nodes/node2.json', {}
+
file 'roles/role1.json', {}
file 'roles/role2.json', {}
file 'users/user1.json', {}
@@ -593,7 +597,7 @@ EOM
end
it "knife list -Rfp / returns everything" do
- knife('list -Rp --local --flat /').should_succeed <<EOM
+ knife('list -Rp --local --flat /').should_succeed <<-EOM
/clients/
/clients/client1.json
/clients/client2.json
@@ -653,7 +657,7 @@ EOM
context 'and is empty' do
it "knife list / returns all top level directories" do
- knife('list /').should_succeed <<EOM
+ knife('list /').should_succeed <<-EOM
/acls
/clients
/containers
@@ -665,12 +669,14 @@ EOM
/members.json
/nodes
/org.json
+/policies
+/policy_groups
/roles
EOM
end
it "knife list -R / returns everything" do
- knife('list -R /').should_succeed <<EOM
+ knife('list -R /').should_succeed <<-EOM
/:
acls
clients
@@ -683,6 +689,8 @@ invitations.json
members.json
nodes
org.json
+policies
+policy_groups
roles
/acls:
@@ -760,23 +768,17 @@ users.json
/nodes:
+/policies:
+
+/policy_groups:
+
/roles:
EOM
end
end
- end
-
- when_the_chef_server "is in Enterprise mode", :osc_compat => false, :single_org => false do
- before do
- organization 'foo'
- end
-
- before :each do
- Chef::Config.chef_server_url = URI.join(Chef::Config.chef_server_url, '/organizations/foo')
- end
it 'knife list -R / returns everything' do
- knife('list -R /').should_succeed <<EOM
+ knife('list -R /').should_succeed <<-EOM
/:
acls
clients
@@ -789,6 +791,8 @@ invitations.json
members.json
nodes
org.json
+policies
+policy_groups
roles
/acls:
@@ -866,8 +870,149 @@ users.json
/nodes:
+/policies:
+
+/policy_groups:
+
/roles:
EOM
end
+
+ context "has plenty of stuff in it" do
+ before do
+ client 'client1', {}
+ client 'client2', {}
+ container 'container1', {}
+ container 'container2', {}
+ cookbook 'cookbook1', '1.0.0'
+ cookbook 'cookbook2', '1.0.1', { 'recipes' => { 'default.rb' => '' } }
+ data_bag 'bag1', { 'item1' => {}, 'item2' => {} }
+ data_bag 'bag2', { 'item1' => {}, 'item2' => {} }
+ environment 'environment1', {}
+ environment 'environment2', {}
+ group 'group1', {}
+ group 'group2', {}
+ node 'node1', {}
+ node 'node2', {}
+ org_invite 'user1'
+ org_member 'user2'
+ policy 'policy1', '1.2.3', {}
+ policy 'policy2', '1.2.3', {}
+ policy 'policy2', '1.3.5', {}
+ policy_group 'policy_group1', { 'policies' => { 'policy1' => { 'revision_id' => '1.2.3' } } }
+ policy_group 'policy_group2', { 'policies' => { 'policy2' => { 'revision_id' => '1.3.5' } } }
+ role 'role1', {}
+ role 'role2', {}
+ user 'user1', {}
+ user 'user2', {}
+ end
+
+ it "knife list -Rfp / returns everything" do
+ knife('list -Rfp /').should_succeed <<-EOM
+/acls/
+/acls/clients/
+/acls/clients/client1.json
+/acls/clients/client2.json
+/acls/clients/foo-validator.json
+/acls/containers/
+/acls/containers/clients.json
+/acls/containers/container1.json
+/acls/containers/container2.json
+/acls/containers/containers.json
+/acls/containers/cookbook_artifacts.json
+/acls/containers/cookbooks.json
+/acls/containers/data.json
+/acls/containers/environments.json
+/acls/containers/groups.json
+/acls/containers/nodes.json
+/acls/containers/policies.json
+/acls/containers/roles.json
+/acls/containers/sandboxes.json
+/acls/cookbooks/
+/acls/cookbooks/cookbook1.json
+/acls/cookbooks/cookbook2.json
+/acls/data_bags/
+/acls/data_bags/bag1.json
+/acls/data_bags/bag2.json
+/acls/environments/
+/acls/environments/_default.json
+/acls/environments/environment1.json
+/acls/environments/environment2.json
+/acls/groups/
+/acls/groups/admins.json
+/acls/groups/billing-admins.json
+/acls/groups/clients.json
+/acls/groups/group1.json
+/acls/groups/group2.json
+/acls/groups/users.json
+/acls/nodes/
+/acls/nodes/node1.json
+/acls/nodes/node2.json
+/acls/organization.json
+/acls/roles/
+/acls/roles/role1.json
+/acls/roles/role2.json
+/clients/
+/clients/client1.json
+/clients/client2.json
+/clients/foo-validator.json
+/containers/
+/containers/clients.json
+/containers/container1.json
+/containers/container2.json
+/containers/containers.json
+/containers/cookbook_artifacts.json
+/containers/cookbooks.json
+/containers/data.json
+/containers/environments.json
+/containers/groups.json
+/containers/nodes.json
+/containers/policies.json
+/containers/roles.json
+/containers/sandboxes.json
+/cookbooks/
+/cookbooks/cookbook1/
+/cookbooks/cookbook1/metadata.rb
+/cookbooks/cookbook2/
+/cookbooks/cookbook2/metadata.rb
+/cookbooks/cookbook2/recipes/
+/cookbooks/cookbook2/recipes/default.rb
+/data_bags/
+/data_bags/bag1/
+/data_bags/bag1/item1.json
+/data_bags/bag1/item2.json
+/data_bags/bag2/
+/data_bags/bag2/item1.json
+/data_bags/bag2/item2.json
+/environments/
+/environments/_default.json
+/environments/environment1.json
+/environments/environment2.json
+/groups/
+/groups/admins.json
+/groups/billing-admins.json
+/groups/clients.json
+/groups/group1.json
+/groups/group2.json
+/groups/users.json
+/invitations.json
+/members.json
+/nodes/
+/nodes/node1.json
+/nodes/node2.json
+/org.json
+/policies/
+/policies/policy1-1.2.3.json
+/policies/policy2-1.2.3.json
+/policies/policy2-1.3.5.json
+/policy_groups/
+/policy_groups/policy_group1.json
+/policy_groups/policy_group2.json
+/roles/
+/roles/role1.json
+/roles/role2.json
+EOM
+ end
+ end
end
end
diff --git a/spec/integration/knife/upload_spec.rb b/spec/integration/knife/upload_spec.rb
index d72cbb82f5..f7d4fb3cd4 100644
--- a/spec/integration/knife/upload_spec.rb
+++ b/spec/integration/knife/upload_spec.rb
@@ -1248,6 +1248,7 @@ EOM
end
end # with versioned cookbooks
+
when_the_chef_server 'has a user' do
before do
user 'x', {}
@@ -1268,7 +1269,7 @@ EOM
user 'foo', {}
user 'bar', {}
user 'foobar', {}
- organization 'foo', { 'full_name' => 'Something'}
+ organization 'foo', { 'full_name' => 'Something' }
end
before :each do
@@ -1278,7 +1279,7 @@ EOM
context 'and has nothing but a single group named blah' do
group 'blah', {}
- when_the_repository 'has one of each thing' do
+ when_the_repository 'has at least one of each thing' do
before do
# TODO We have to upload acls for an existing group due to a lack of
@@ -1292,8 +1293,11 @@ EOM
file 'groups/x.json', {}
file 'invitations.json', [ 'foo' ]
file 'members.json', [ 'bar' ]
- file 'nodes/x.json', {}
file 'org.json', { 'full_name' => 'wootles' }
+ file 'nodes/x.json', {}
+ file 'policies/x-1.0.0.json', { }
+ file 'policies/blah-1.0.0.json', { }
+ file 'policy_groups/x.json', { 'policies' => { 'x' => { 'revision_id' => '1.0.0' }, 'blah' => { 'revision_id' => '1.0.0' } } }
file 'roles/x.json', {}
end
@@ -1311,11 +1315,102 @@ Updated /invitations.json
Updated /members.json
Created /nodes/x.json
Updated /org.json
+Created /policies/blah-1.0.0.json
+Created /policies/x-1.0.0.json
+Created /policy_groups/x.json
Created /roles/x.json
EOM
expect(api.get('association_requests').map { |a| a['username'] }).to eq([ 'foo' ])
expect(api.get('users').map { |a| a['user']['username'] }).to eq([ 'bar' ])
end
+
+ context "When the chef server has an identical copy of each thing" do
+ before do
+ file 'invitations.json', [ 'foo' ]
+ file 'members.json', [ 'bar' ]
+ file 'org.json', { 'full_name' => 'Something' }
+
+ # acl_for %w(organizations foo groups blah)
+ client 'x', {}
+ cookbook 'x', '1.0.0'
+ container 'x', {}
+ data_bag 'x', { 'y' => {} }
+ environment 'x', {}
+ group 'x', {}
+ org_invite 'foo'
+ org_member 'bar'
+ node 'x', {}
+ policy 'x', '1.0.0', {}
+ policy 'blah', '1.0.0', {}
+ policy_group 'x', {
+ 'policies' => {
+ 'x' => { 'revision_id' => '1.0.0' },
+ 'blah' => { 'revision_id' => '1.0.0' }
+ }
+ }
+ role 'x', {}
+ end
+
+ it 'knife upload makes no changes' do
+ knife('upload /').should_succeed <<EOM
+Updated /acls/groups/blah.json
+EOM
+ end
+ end
+
+ context "When the chef server has a slightly different copy of the policy revision" do
+ before do
+ policy 'x', '1.0.0', { 'run_list' => [ 'blah' ] }
+ end
+
+ it "should fail because policies are not updateable" do
+ knife("upload /policies/x-1.0.0.json").should_fail <<EOM
+ERROR: /policies/x-1.0.0.json cannot be updated: policy revisions are immutable once uploaded. If you want to change the policy, create a new revision with your changes.
+EOM
+ end
+ end
+
+ context "When the chef server has a slightly different copy of each thing (except policy revisions)" do
+ before do
+ # acl_for %w(organizations foo groups blah)
+ client 'x', { 'validator' => true }
+ container 'x', {}
+ cookbook 'x', '1.0.0', { 'recipes' => { 'default.rb' => '' } }
+ data_bag 'x', { 'y' => { 'a' => 'b' } }
+ environment 'x', { 'description' => 'foo' }
+ group 'x', { 'groups' => [ 'admin' ] }
+ node 'x', { 'run_list' => [ 'blah' ] }
+ policy 'x', '1.0.0', { }
+ policy 'x', '1.0.1', { }
+ policy 'y', '1.0.0', { }
+ policy_group 'x', {
+ 'policies' => {
+ 'x' => { 'revision_id' => '1.0.1' },
+ 'y' => { 'revision_id' => '1.0.0' }
+ }
+ }
+ role 'x', { 'run_list' => [ 'blah' ] }
+ end
+
+ it 'knife upload updates everything' do
+ knife('upload /').should_succeed <<EOM
+Updated /acls/groups/blah.json
+Updated /clients/x.json
+Updated /cookbooks/x
+Updated /data_bags/x/y.json
+Updated /environments/x.json
+Updated /groups/x.json
+Updated /invitations.json
+Updated /members.json
+Updated /nodes/x.json
+Updated /org.json
+Created /policies/blah-1.0.0.json
+Updated /policy_groups/x.json
+Updated /roles/x.json
+EOM
+ knife('diff --name-status --diff-filter=AMT /').should_succeed ''
+ end
+ end
end
when_the_repository 'has an org.json that does not change full_name' do
diff --git a/spec/unit/chef_fs/config_spec.rb b/spec/unit/chef_fs/config_spec.rb
index c7c47ad8ab..c9cf756dc0 100644
--- a/spec/unit/chef_fs/config_spec.rb
+++ b/spec/unit/chef_fs/config_spec.rb
@@ -102,9 +102,5 @@ describe Chef::ChefFS::Config do
it "sets the correct user path on the local FS object" do
expect(local_fs.child_paths["users"]).to eq([platform_path("/base_path/users")])
end
-
- it "sets the correct policy path on the local FS object" do
- expect(local_fs.child_paths["policies"]).to eq([platform_path("/base_path/policies")])
- end
end
end