summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Keiser <jkeiser@opscode.com>2014-08-31 10:07:21 -0700
committerJohn Keiser <jkeiser@opscode.com>2014-09-05 12:09:08 -0700
commit865ae8a61ccb3395c7f738871be0bf7a2d1b02ce (patch)
tree2e60dd013042dd4091a30206e33d5b7becce5b8e
parent27b05b2396459f3d84f0ebd924f8adc44a906cd1 (diff)
downloadchef-865ae8a61ccb3395c7f738871be0bf7a2d1b02ce.tar.gz
Add org.json, members.json, invitations.json for full org download
and upload support
-rw-r--r--lib/chef/chef_fs/config.rb2
-rw-r--r--lib/chef/chef_fs/data_handler/organization_data_handler.rb30
-rw-r--r--lib/chef/chef_fs/data_handler/organization_invites_data_handler.rb17
-rw-r--r--lib/chef/chef_fs/data_handler/organization_members_data_handler.rb17
-rw-r--r--lib/chef/chef_fs/file_system.rb1
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb6
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb49
-rw-r--r--lib/chef/chef_fs/file_system/chef_server_root_dir.rb19
-rw-r--r--lib/chef/chef_fs/file_system/org_entry.rb34
-rw-r--r--lib/chef/chef_fs/file_system/organization_invites_entry.rb59
-rw-r--r--lib/chef/chef_fs/file_system/organization_members_entry.rb58
-rw-r--r--lib/chef/chef_fs/file_system/rest_list_entry.rb17
-rw-r--r--lib/chef/chef_fs/parallelizer.rb2
-rw-r--r--spec/integration/knife/delete_spec.rb29
-rw-r--r--spec/integration/knife/download_spec.rb76
-rw-r--r--spec/integration/knife/list_spec.rb103
-rw-r--r--spec/integration/knife/upload_spec.rb153
-rw-r--r--spec/support/shared/integration/integration_helper.rb12
18 files changed, 653 insertions, 31 deletions
diff --git a/lib/chef/chef_fs/config.rb b/lib/chef/chef_fs/config.rb
index 536409a109..c9f187a3bd 100644
--- a/lib/chef/chef_fs/config.rb
+++ b/lib/chef/chef_fs/config.rb
@@ -68,7 +68,7 @@ class Chef
def create_local_fs
require 'chef/chef_fs/file_system/chef_repository_file_system_root_dir'
- Chef::ChefFS::FileSystem::ChefRepositoryFileSystemRootDir.new(object_paths)
+ Chef::ChefFS::FileSystem::ChefRepositoryFileSystemRootDir.new(object_paths, Array(chef_config[:chef_repo_path]).flatten)
end
# Returns the given real path's location relative to the server root.
diff --git a/lib/chef/chef_fs/data_handler/organization_data_handler.rb b/lib/chef/chef_fs/data_handler/organization_data_handler.rb
new file mode 100644
index 0000000000..1f2f9ffaf5
--- /dev/null
+++ b/lib/chef/chef_fs/data_handler/organization_data_handler.rb
@@ -0,0 +1,30 @@
+require 'chef/chef_fs/data_handler/data_handler_base'
+
+class Chef
+ module ChefFS
+ module DataHandler
+ class OrganizationDataHandler < DataHandlerBase
+ def normalize(organization, entry)
+ result = normalize_hash(organization, {
+ 'name' => entry.org,
+ 'full_name' => entry.org,
+ 'org_type' => 'Business',
+ 'clientname' => "#{entry.org}-validator",
+ 'billing_plan' => 'platform-free',
+ })
+ result
+ end
+
+ def preserve_key(key)
+ return key == 'name'
+ end
+
+ def verify_integrity(object, entry, &on_error)
+ if entry.org != object['name']
+ on_error.call("Name must be '#{entry.org}' (is '#{object['name']}')")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/data_handler/organization_invites_data_handler.rb b/lib/chef/chef_fs/data_handler/organization_invites_data_handler.rb
new file mode 100644
index 0000000000..db56ecc504
--- /dev/null
+++ b/lib/chef/chef_fs/data_handler/organization_invites_data_handler.rb
@@ -0,0 +1,17 @@
+require 'chef/chef_fs/data_handler/data_handler_base'
+
+class Chef
+ module ChefFS
+ module DataHandler
+ class OrganizationInvitesDataHandler < DataHandlerBase
+ def normalize(invites, entry)
+ invites.map { |invite| invite.is_a?(Hash) ? invite['username'] : invite }.sort.uniq
+ end
+
+ def minimize(invites, entry)
+ invites
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/data_handler/organization_members_data_handler.rb b/lib/chef/chef_fs/data_handler/organization_members_data_handler.rb
new file mode 100644
index 0000000000..afa331775c
--- /dev/null
+++ b/lib/chef/chef_fs/data_handler/organization_members_data_handler.rb
@@ -0,0 +1,17 @@
+require 'chef/chef_fs/data_handler/data_handler_base'
+
+class Chef
+ module ChefFS
+ module DataHandler
+ class OrganizationMembersDataHandler < DataHandlerBase
+ def normalize(members, entry)
+ members.map { |member| member.is_a?(Hash) ? member['user']['username'] : member }.sort.uniq
+ end
+
+ def minimize(members, entry)
+ members
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system.rb b/lib/chef/chef_fs/file_system.rb
index 4d15d7af33..730fa0e5cc 100644
--- a/lib/chef/chef_fs/file_system.rb
+++ b/lib/chef/chef_fs/file_system.rb
@@ -273,7 +273,6 @@ class Chef
# case we shouldn't waste time trying PUT if we know the file doesn't
# exist.
# Will need to decide how that works with checksums, though.
-
error = false
begin
dest_path = format_path.call(dest_entry) if ui
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb
index 6ccdc2cf5f..9acfe4b936 100644
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb
+++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb
@@ -31,8 +31,12 @@ class Chef
@data_handler = data_handler
end
+ def write_pretty_json=(value)
+ @write_pretty_json = value
+ end
+
def write_pretty_json
- root.write_pretty_json
+ @write_pretty_json.nil? ? root.write_pretty_json : @write_pretty_json
end
def data_handler
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 d615e0f415..14f3e8413b 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
@@ -34,31 +34,50 @@ class Chef
module ChefFS
module FileSystem
class ChefRepositoryFileSystemRootDir < BaseFSDir
- def initialize(child_paths)
+ def initialize(child_paths, root_paths=nil)
super("", nil)
@child_paths = child_paths
+ @root_paths = root_paths
end
attr_accessor :write_pretty_json
+ attr_reader :root_paths
attr_reader :child_paths
+ CHILDREN = %w(invitations.json members.json org.json)
+
def children
- @children ||= child_paths.keys.sort.map { |name| make_child_entry(name) }.select { |child| !child.nil? }
+ @children ||= begin
+ result = child_paths.keys.sort.map { |name| make_child_entry(name) }.select { |child| !child.nil? }
+ result += root_dir.children.select { |c| CHILDREN.include?(c.name) } if root_dir
+ result.sort_by { |c| c.name }
+ end
end
def can_have_child?(name, is_dir)
- child_paths.has_key?(name) && is_dir
+ if is_dir
+ child_paths.has_key?(name)
+ elsif root_paths
+ CHILDREN.include?(name)
+ else
+ false
+ end
end
def create_child(name, file_contents = nil)
- child_paths[name].each do |path|
- begin
- Dir.mkdir(path)
- rescue Errno::EEXIST
+ if file_contents
+ d = root_dir
+ child = root_dir.create_child(name, file_contents)
+ else
+ child_paths[name].each do |path|
+ begin
+ Dir.mkdir(path)
+ rescue Errno::EEXIST
+ end
end
+ child = make_child_entry(name)
end
- child = make_child_entry(name)
@children = nil
child
end
@@ -69,15 +88,15 @@ class Chef
# Used to print out the filesystem
def fs_description
- repo_path = File.dirname(child_paths['cookbooks'][0])
- result = "repository at #{repo_path}\n"
+ repo_paths = root_paths || [ File.dirname(child_paths['cookbooks'][0]) ]
+ result = "repository at #{repo_paths.join(', ')}\n"
if Chef::Config[:versioned_cookbooks]
result << " Multiple versions per cookbook\n"
else
result << " One version per cookbook\n"
end
child_paths.each_pair do |name, paths|
- if paths.any? { |path| File.dirname(path) != repo_path }
+ if paths.any? { |path| !repo_paths.include?(File.dirname(path)) }
result << " #{name} at #{paths.join(', ')}\n"
end
end
@@ -86,6 +105,14 @@ class Chef
private
+ def root_dir
+ MultiplexedDir.new(root_paths.select { |path| File.exists?(path) }.map do |path|
+ dir = ChefRepositoryFileSystemEntry.new(name, parent, path)
+ dir.write_pretty_json = !!write_pretty_json
+ dir
+ end)
+ end
+
def make_child_entry(name)
paths = child_paths[name].select do |path|
File.exists?(path)
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 0083ee4cfa..069a2e29fa 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
@@ -23,6 +23,9 @@ require 'chef/chef_fs/file_system/rest_list_dir'
require 'chef/chef_fs/file_system/cookbooks_dir'
require 'chef/chef_fs/file_system/data_bags_dir'
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/environments_dir'
require 'chef/chef_fs/data_handler/client_data_handler'
require 'chef/chef_fs/data_handler/role_data_handler'
@@ -81,10 +84,13 @@ class Chef
end
def org
- @org ||= if URI.parse(chef_server_url).path =~ /^\/+organizations\/+([^\/]+)$/
- $1
- else
- nil
+ @org ||= begin
+ path = Pathname.new(URI.parse(chef_server_url).path).cleanpath
+ if File.dirname(path) == '/organizations'
+ File.basename(path)
+ else
+ nil
+ end
end
end
@@ -102,7 +108,10 @@ class Chef
RestListDir.new("clients", self, nil, Chef::ChefFS::DataHandler::ClientDataHandler.new),
RestListDir.new("containers", self, nil, Chef::ChefFS::DataHandler::ContainerDataHandler.new),
RestListDir.new("groups", self, nil, Chef::ChefFS::DataHandler::GroupDataHandler.new),
- NodesDir.new(self)
+ NodesDir.new(self),
+ OrgEntry.new("org.json", self),
+ OrganizationMembersEntry.new("members.json", self),
+ OrganizationInvitesEntry.new("invitations.json", self)
]
elsif repo_mode != 'static'
result += [
diff --git a/lib/chef/chef_fs/file_system/org_entry.rb b/lib/chef/chef_fs/file_system/org_entry.rb
new file mode 100644
index 0000000000..852956e1e5
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/org_entry.rb
@@ -0,0 +1,34 @@
+require 'chef/chef_fs/file_system/rest_list_entry'
+require 'chef/chef_fs/data_handler/organization_data_handler'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ # /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
+
+ # /organizations/foo/org.json -> GET /organizations/foo
+ def api_path
+ parent.api_path
+ end
+
+ def exists?
+ parent.exists?
+ end
+
+ def delete(recurse)
+ raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/organization_invites_entry.rb b/lib/chef/chef_fs/file_system/organization_invites_entry.rb
new file mode 100644
index 0000000000..a83e058b77
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/organization_invites_entry.rb
@@ -0,0 +1,59 @@
+require 'chef/chef_fs/file_system/rest_list_entry'
+require 'chef/chef_fs/data_handler/organization_invites_data_handler'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ # /organizations/NAME/invitations.json
+ # Represents the actual data at
+ # read:
+ # - GET /organizations/NAME/association_requests
+ # write:
+ # - remove from list: DELETE /organizations/NAME/association_requests/id
+ # - add to list: POST /organizations/NAME/association_requests
+ class OrganizationInvitesEntry < RestListEntry
+ def initialize(name, parent, exists = nil)
+ super(name, parent)
+ @exists = exists
+ end
+
+ def data_handler
+ Chef::ChefFS::DataHandler::OrganizationInvitesDataHandler.new
+ end
+
+ # /organizations/foo/invites.json -> /organizations/foo/association_requests
+ def api_path
+ File.join(parent.api_path, 'association_requests')
+ end
+
+ def exists?
+ parent.exists?
+ end
+
+ def delete(recurse)
+ raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self)
+ end
+
+ def write(contents)
+ desired_invites = minimize_value(JSON.parse(contents, :create_additions => false))
+ actual_invites = _read_json.inject({}) { |h,val| h[val['username']] = val['id']; h }
+ invites = actual_invites.keys
+ (desired_invites - invites).each do |invite|
+ begin
+ rest.post(api_path, { 'user' => invite })
+ rescue Net::HTTPServerException => e
+ if e.response.code == '409'
+ Chef::Log.warn("Could not invite #{invite} to organization #{org}: #{api_error_text(e.response)}")
+ else
+ raise
+ end
+ end
+ end
+ (invites - desired_invites).each do |invite|
+ rest.delete(File.join(api_path, actual_invites[invite]))
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/organization_members_entry.rb b/lib/chef/chef_fs/file_system/organization_members_entry.rb
new file mode 100644
index 0000000000..c1151413f5
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/organization_members_entry.rb
@@ -0,0 +1,58 @@
+require 'chef/chef_fs/file_system/rest_list_entry'
+require 'chef/chef_fs/data_handler/organization_members_data_handler'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ # /organizations/NAME/members.json
+ # Represents the actual data at
+ # read:
+ # - GET /organizations/NAME/users
+ # write:
+ # - remove from list: DELETE /organizations/NAME/users/name
+ # - add to list: POST /organizations/NAME/users/name
+ class OrganizationMembersEntry < RestListEntry
+ def initialize(name, parent, exists = nil)
+ super(name, parent)
+ @exists = exists
+ end
+
+ def data_handler
+ Chef::ChefFS::DataHandler::OrganizationMembersDataHandler.new
+ end
+
+ # /organizations/foo/members.json -> /organizations/foo/users
+ def api_path
+ File.join(parent.api_path, 'users')
+ end
+
+ def exists?
+ parent.exists?
+ end
+
+ def delete(recurse)
+ raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self)
+ end
+
+ def write(contents)
+ desired_members = minimize_value(JSON.parse(contents, :create_additions => false))
+ members = minimize_value(_read_json)
+ (desired_members - members).each do |member|
+ begin
+ rest.post(File.join(api_path, member), {})
+ rescue Net::HTTPServerException => e
+ if e.response.code == '404'
+ raise "Chef server at #{api_path} does not allow you to directly add members. Please either upgrade your Chef server or move the users you want into invitations.json instead of members.json."
+ else
+ raise
+ end
+ end
+ end
+ (members - desired_members).each do |member|
+ rest.delete(File.join(api_path, member))
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/rest_list_entry.rb b/lib/chef/chef_fs/file_system/rest_list_entry.rb
index 67252a6f2f..ac47ff4f25 100644
--- a/lib/chef/chef_fs/file_system/rest_list_entry.rb
+++ b/lib/chef/chef_fs/file_system/rest_list_entry.rb
@@ -80,13 +80,13 @@ class Chef
end
def read
- Chef::JSONCompat.to_json_pretty(_read_hash)
+ Chef::JSONCompat.to_json_pretty(minimize_value(_read_json))
end
- def _read_hash
+ def _read_json
begin
# Minimize the value (get rid of defaults) so the results don't look terrible
- minimize_value(root.get_json(api_path))
+ root.get_json(api_path)
rescue Timeout::Error => e
raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "Timeout reading: #{e}"
rescue Net::HTTPServerException => e
@@ -119,7 +119,7 @@ class Chef
# Grab this value
begin
- value = _read_hash
+ value = _read_json
rescue Chef::ChefFS::FileSystem::NotFoundError
return [ false, :none, other_value_json ]
end
@@ -169,7 +169,16 @@ class Chef
end
end
end
+
+ def api_error_text(response)
+ begin
+ JSON.parse(response.body)['error'].join("\n")
+ rescue
+ response.body
+ end
+ end
end
+
end
end
end
diff --git a/lib/chef/chef_fs/parallelizer.rb b/lib/chef/chef_fs/parallelizer.rb
index 116a626869..f29a2deae7 100644
--- a/lib/chef/chef_fs/parallelizer.rb
+++ b/lib/chef/chef_fs/parallelizer.rb
@@ -53,6 +53,7 @@ class Chef
end
def resize(to_threads, wait = true, timeout = nil)
+ to_threads = 0
if to_threads < num_threads
threads_to_stop = @threads[to_threads..num_threads-1]
@threads = @threads.slice(0, to_threads)
@@ -89,6 +90,7 @@ class Chef
begin
while !@stop_thread[Thread.current]
begin
+ puts "Got a task!"
task = @tasks.pop
task.call
rescue
diff --git a/spec/integration/knife/delete_spec.rb b/spec/integration/knife/delete_spec.rb
index dc52cc71a4..a9ba9dc3e1 100644
--- a/spec/integration/knife/delete_spec.rb
+++ b/spec/integration/knife/delete_spec.rb
@@ -962,9 +962,12 @@ EOM
end
end
- when_the_chef_server "is in Enterprise mode", :osc_compat => false, :single_org => false do
+ when_the_chef_server "is in Enterprise mode", :focus, :osc_compat => false, :single_org => false do
before do
- organization 'foo'
+ organization 'foo' do
+ container 'x', {}
+ group 'x', {}
+ end
end
before :each do
@@ -974,5 +977,27 @@ EOM
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"
end
+
+ it 'knife delete /containers/x.json succeeds' do
+ knife('delete /containers/x.json').should_succeed "Deleted /containers/x.json\n"
+ knife('raw /containers/x.json').should_fail(/404/)
+ end
+
+ it 'knife delete /groups/x.json succeeds' do
+ knife('delete /groups/x.json').should_succeed "Deleted /groups/x.json\n"
+ knife('raw /groups/x.json').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
+
+ it 'knife delete /invitations.json fails with a reasonable error' do
+ knife('delete /invitations.json').should_fail "ERROR: /invitations.json (remote) cannot be deleted.\n"
+ end
+
+ it 'knife delete /members.json fails with a reasonable error' do
+ knife('delete /members.json').should_fail "ERROR: /members.json (remote) cannot be deleted.\n"
+ end
end
end
diff --git a/spec/integration/knife/download_spec.rb b/spec/integration/knife/download_spec.rb
index 4126acc9fc..17779d1f47 100644
--- a/spec/integration/knife/download_spec.rb
+++ b/spec/integration/knife/download_spec.rb
@@ -1091,4 +1091,80 @@ EOM
end
end
end
+
+ 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
+ end
+
+ before :each do
+ Chef::Config.chef_server_url = URI.join(Chef::Config.chef_server_url, '/organizations/foo')
+ end
+
+ when_the_repository 'is empty' do
+ it 'knife download / downloads everything' do
+ knife('download /').should_succeed <<EOM
+Created /acls
+Created /acls/clients
+Created /acls/clients/foo-validator.json
+Created /acls/containers
+Created /acls/containers/clients.json
+Created /acls/containers/containers.json
+Created /acls/containers/cookbooks.json
+Created /acls/containers/data.json
+Created /acls/containers/environments.json
+Created /acls/containers/groups.json
+Created /acls/containers/nodes.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
+Created /acls/environments/_default.json
+Created /acls/groups
+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
+Created /clients
+Created /clients/foo-validator.json
+Created /containers
+Created /containers/clients.json
+Created /containers/containers.json
+Created /containers/cookbooks.json
+Created /containers/data.json
+Created /containers/environments.json
+Created /containers/groups.json
+Created /containers/nodes.json
+Created /containers/roles.json
+Created /containers/sandboxes.json
+Created /containers/x.json
+Created /cookbooks
+Created /data_bags
+Created /environments
+Created /environments/_default.json
+Created /groups
+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 /roles
+EOM
+ knife('diff --name-status /').should_succeed ''
+ end
+ end
+ end
end
diff --git a/spec/integration/knife/list_spec.rb b/spec/integration/knife/list_spec.rb
index 1f70d3bc3d..48e85f43bb 100644
--- a/spec/integration/knife/list_spec.rb
+++ b/spec/integration/knife/list_spec.rb
@@ -651,7 +651,7 @@ EOM
Chef::Config.chef_server_url = URI.join(Chef::Config.chef_server_url, '/organizations/foo')
end
- context 'and has plenty of stuff in it' do
+ context 'and is empty' do
it "knife list / returns all top level directories" do
knife('list /').should_succeed <<EOM
/acls
@@ -755,4 +755,105 @@ 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
+/:
+acls
+clients
+containers
+cookbooks
+data_bags
+environments
+groups
+invitations.json
+members.json
+nodes
+org.json
+roles
+
+/acls:
+clients
+containers
+cookbooks
+data_bags
+environments
+groups
+nodes
+organization.json
+roles
+
+/acls/clients:
+foo-validator.json
+
+/acls/containers:
+clients.json
+containers.json
+cookbooks.json
+data.json
+environments.json
+groups.json
+nodes.json
+roles.json
+sandboxes.json
+
+/acls/cookbooks:
+
+/acls/data_bags:
+
+/acls/environments:
+_default.json
+
+/acls/groups:
+admins.json
+billing-admins.json
+clients.json
+users.json
+
+/acls/nodes:
+
+/acls/roles:
+
+/clients:
+foo-validator.json
+
+/containers:
+clients.json
+containers.json
+cookbooks.json
+data.json
+environments.json
+groups.json
+nodes.json
+roles.json
+sandboxes.json
+
+/cookbooks:
+
+/data_bags:
+
+/environments:
+_default.json
+
+/groups:
+admins.json
+billing-admins.json
+clients.json
+users.json
+
+/nodes:
+
+/roles:
+EOM
+ end
+ end
end
diff --git a/spec/integration/knife/upload_spec.rb b/spec/integration/knife/upload_spec.rb
index 4e447589f3..05d33d1a17 100644
--- a/spec/integration/knife/upload_spec.rb
+++ b/spec/integration/knife/upload_spec.rb
@@ -1216,4 +1216,157 @@ EOM
end
end
end
+
+ when_the_chef_server "is in Enterprise mode", :osc_compat => false, :single_org => false do
+ before do
+ 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
+
+ context 'and has nothing but a single group named blah' do
+ group 'blah', {}
+
+ when_the_repository 'has one 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 'acls/groups/blah.json', {}
+ 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' => 'wootles' }
+ file 'roles/x.json', {}
+ end
+
+ it 'knife upload / uploads everything' do
+ knife('upload /').should_succeed <<EOM
+Updated /acls/groups/blah.json
+Created /clients/x.json
+Created /containers/x.json
+Created /cookbooks/x
+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
+Updated /org.json
+Created /roles/x.json
+EOM
+ api.get('association_requests').map { |a| a['username'] }.should == [ 'foo' ]
+ api.get('users').map { |a| a['user']['username'] }.should == [ 'bar' ]
+ end
+ end
+
+ when_the_repository 'has an org.json that does not change full_name' do
+ before do
+ file 'org.json', { 'full_name' => 'Something' }
+ end
+
+ it 'knife upload / emits a warning for bar and adds foo and foobar' do
+ knife('upload /').should_succeed ''
+ api.get('/')['full_name'].should == 'Something'
+ end
+ end
+
+ when_the_repository 'has an org.json that changes full_name' do
+ before do
+ file 'org.json', { 'full_name' => 'Something Else'}
+ end
+
+ it 'knife upload / emits a warning for bar and adds foo and foobar' do
+ knife('upload /').should_succeed "Updated /org.json\n"
+ api.get('/')['full_name'].should == 'Something Else'
+ end
+ end
+
+ context 'and has invited foo and bar is already a member' do
+ org_invite 'foo'
+ org_member 'bar'
+
+ when_the_repository 'wants to invite foo, bar and foobar' do
+ before do
+ file 'invitations.json', [ 'foo', 'bar', 'foobar' ]
+ end
+
+ it 'knife upload / emits a warning for bar and invites foobar' do
+ knife('upload /').should_succeed "Updated /invitations.json\n", :stderr => "WARN: Could not invite bar to organization foo: User bar is already in organization foo\n"
+ api.get('association_requests').map { |a| a['username'] }.should == [ 'foo', 'foobar' ]
+ api.get('users').map { |a| a['user']['username'] }.should == [ 'bar' ]
+ end
+ end
+
+ when_the_repository 'wants to make foo, bar and foobar members' do
+ before do
+ file 'members.json', [ 'foo', 'bar', 'foobar' ]
+ end
+
+ it 'knife upload / emits a warning for bar and adds foo and foobar' do
+ knife('upload /').should_succeed "Updated /members.json\n"
+ api.get('association_requests').map { |a| a['username'] }.should == [ ]
+ api.get('users').map { |a| a['user']['username'] }.should == [ 'bar', 'foo', 'foobar' ]
+ end
+ end
+
+ when_the_repository 'wants to invite foo and have bar as a member' do
+ before do
+ file 'invitations.json', [ 'foo' ]
+ file 'members.json', [ 'bar' ]
+ end
+
+ it 'knife upload / does nothing' do
+ knife('upload /').should_succeed ''
+ api.get('association_requests').map { |a| a['username'] }.should == [ 'foo' ]
+ api.get('users').map { |a| a['user']['username'] }.should == [ 'bar' ]
+ end
+ end
+ end
+
+ context 'and has invited bar and foo' do
+ org_invite 'bar', 'foo'
+
+ when_the_repository 'wants to invite foo and bar (different order)' do
+ before do
+ file 'invitations.json', [ 'foo', 'bar' ]
+ end
+
+ it 'knife upload / does nothing' do
+ knife('upload /').should_succeed ''
+ api.get('association_requests').map { |a| a['username'] }.should == [ 'bar', 'foo' ]
+ api.get('users').map { |a| a['user']['username'] }.should == [ ]
+ end
+ end
+ end
+
+ context 'and has already added bar and foo as members of the org' do
+ org_member 'bar', 'foo'
+
+ when_the_repository 'wants to add foo and bar (different order)' do
+ before do
+ file 'members.json', [ 'foo', 'bar' ]
+ end
+
+ it 'knife upload / does nothing' do
+ knife('upload /').should_succeed ''
+ api.get('association_requests').map { |a| a['username'] }.should == [ ]
+ api.get('users').map { |a| a['user']['username'] }.should == [ 'bar', 'foo' ]
+ end
+ end
+ end
+ end
+ end
end
diff --git a/spec/support/shared/integration/integration_helper.rb b/spec/support/shared/integration/integration_helper.rb
index a6595e778d..465633b9e0 100644
--- a/spec/support/shared/integration/integration_helper.rb
+++ b/spec/support/shared/integration/integration_helper.rb
@@ -20,9 +20,9 @@
require 'tmpdir'
require 'fileutils'
require 'chef/config'
-require 'chef_zero/rspec'
-
require 'chef/json_compat'
+require 'chef/server_api'
+require 'chef_zero/rspec'
require 'support/shared/integration/knife_support'
require 'support/shared/integration/app_server_support'
require 'spec_helper'
@@ -53,6 +53,10 @@ module IntegrationSupport
includer_class.extend(ClassMethods)
end
+ def api
+ Chef::ServerAPI.new
+ end
+
def directory(relative_path, &block)
old_parent_path = @parent_path
@parent_path = path_to(relative_path)
@@ -67,10 +71,8 @@ module IntegrationSupport
FileUtils.mkdir_p(dir) unless dir == '.'
File.open(filename, 'w') do |file|
raw = case contents
- when Hash
+ when Hash, Array
JSON.pretty_generate(contents)
- when Array
- contents.join("\n")
else
contents
end