spec/integration/upload_spec.rb in knife-18.2.7 vs spec/integration/upload_spec.rb in knife-18.3.0

- old
+ new

@@ -1,1616 +1,1616 @@ -# -# Author:: John Keiser (<jkeiser@chef.io>) -# Copyright:: Copyright (c) Chef Software 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 "knife_spec_helper" -require "support/shared/integration/integration_helper" -require "chef/knife/upload" -require "chef/knife/diff" -require "chef/knife/raw" -require "chef/json_compat" - -describe "knife upload", :workstation do - include IntegrationSupport - include KnifeSupport - - context "without versioned cookbooks" do - - when_the_chef_server "has one of each thing" do - - before do - client "x", {} - cookbook "x", "1.0.0" - data_bag "x", { "y" => {} } - environment "x", {} - node "x", {} - role "x", {} - user "x", {} - end - - when_the_repository "has only top-level directories" do - before do - directory "clients" - directory "cookbooks" - directory "data_bags" - directory "environments" - directory "nodes" - directory "roles" - directory "users" - end - - it "knife upload does nothing" do - knife("upload /").should_succeed "" - knife("diff --name-status /").should_succeed <<~EOM - D\t/clients/chef-validator.json - D\t/clients/chef-webui.json - D\t/clients/x.json - D\t/cookbooks/x - D\t/data_bags/x - D\t/environments/_default.json - D\t/environments/x.json - D\t/nodes/x.json - D\t/roles/x.json - D\t/users/admin.json - D\t/users/x.json - EOM - end - - it "knife upload --purge deletes everything" do - knife("upload --purge /").should_succeed(<<~EOM, stderr: "WARNING: /environments/_default.json cannot be deleted (default environment cannot be modified).\n") - Deleted extra entry /clients/chef-validator.json (purge is on) - Deleted extra entry /clients/chef-webui.json (purge is on) - Deleted extra entry /clients/x.json (purge is on) - Deleted extra entry /cookbooks/x (purge is on) - Deleted extra entry /data_bags/x (purge is on) - Deleted extra entry /environments/x.json (purge is on) - Deleted extra entry /nodes/x.json (purge is on) - Deleted extra entry /roles/x.json (purge is on) - Deleted extra entry /users/admin.json (purge is on) - Deleted extra entry /users/x.json (purge is on) - EOM - knife("diff --name-status /").should_succeed <<~EOM - D\t/environments/_default.json - EOM - end - end - - when_the_repository "has an identical copy of each thing" do - - before do - file "clients/chef-validator.json", { "validator" => true, "public_key" => ChefZero::PUBLIC_KEY } - file "clients/chef-webui.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY } - file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY } - file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0") - file "data_bags/x/y.json", {} - file "environments/_default.json", { "description" => "The default Chef environment" } - file "environments/x.json", {} - file "nodes/x.json", { "normal" => { "tags" => [] } } - file "roles/x.json", {} - file "users/admin.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY } - file "users/x.json", { "public_key" => ChefZero::PUBLIC_KEY } - end - - it "knife upload makes no changes" do - knife("upload /cookbooks/x").should_succeed "" - knife("diff --name-status /").should_succeed "" - end - - it "knife upload --purge makes no changes" do - knife("upload --purge /").should_succeed "" - knife("diff --name-status /").should_succeed "" - end - - context "except the role file" do - before do - file "roles/x.json", { "description" => "blarghle" } - end - - it "knife upload changes the role" do - knife("upload /").should_succeed "Updated /roles/x.json\n" - knife("diff --name-status /").should_succeed "" - end - it "knife upload --no-diff does not change the role" do - knife("upload --no-diff /").should_succeed "" - knife("diff --name-status /").should_succeed "M\t/roles/x.json\n" - end - end - - context "except the role file is textually different, but not ACTUALLY different" do - before do - file "roles/x.json", <<~EOM - { - "chef_type": "role", - "default_attributes": { - }, - "env_run_lists": { - }, - "json_class": "Chef::Role", - "name": "x", - "description": "", - "override_attributes": { - }, - "run_list": [ - - ] - } - EOM - end - - it "knife upload / does not change anything" do - knife("upload /").should_succeed "" - knife("diff --name-status /").should_succeed "" - end - end - - context "the role is in ruby" do - before do - file "roles/x.rb", <<~EOM - name "x" - description "blargle" - EOM - end - - it "knife upload changes the role" do - knife("upload /").should_succeed "Updated /roles/x.json\n" - knife("diff --name-status /").should_succeed "" - end - - it "knife upload --no-diff does not change the role" do - knife("upload --no-diff /").should_succeed "" - knife("diff --name-status /").should_succeed "M\t/roles/x.rb\n" - end - end - - context "when cookbook metadata has a self-dependency" do - before do - file "cookbooks/x/metadata.rb", "name 'x'; version '1.0.0'; depends 'x'" - end - - it "fails with RuntimeError" do - expect { knife("upload /cookbooks") }.to raise_error RuntimeError, /Cookbook depends on itself/ - end - end - - context "as well as one extra copy of each thing" do - before do - file "clients/y.json", { "public_key" => ChefZero::PUBLIC_KEY } - file "cookbooks/x/blah.rb", "" - file "cookbooks/y/metadata.rb", cb_metadata("y", "1.0.0") - file "data_bags/x/z.json", {} - file "data_bags/y/zz.json", {} - file "environments/y.json", {} - file "nodes/y.json", {} - file "roles/y.json", {} - file "users/y.json", { "public_key" => ChefZero::PUBLIC_KEY } - end - - it "knife upload adds the new files" do - knife("upload /").should_succeed <<~EOM - Created /clients/y.json - Updated /cookbooks/x - Created /cookbooks/y - Created /data_bags/x/z.json - Created /data_bags/y - Created /data_bags/y/zz.json - Created /environments/y.json - Created /nodes/y.json - Created /roles/y.json - Created /users/y.json - EOM - knife("diff --name-status /").should_succeed <<~EOM - D\t/cookbooks/x/metadata.json - D\t/cookbooks/y/metadata.json - EOM - end - - it "knife upload --no-diff adds the new files" do - knife("upload --no-diff /").should_succeed <<~EOM - Created /clients/y.json - Updated /cookbooks/x - Created /cookbooks/y - Created /data_bags/x/z.json - Created /data_bags/y - Created /data_bags/y/zz.json - Created /environments/y.json - Created /nodes/y.json - Created /roles/y.json - Created /users/y.json - EOM - knife("diff --name-status /").should_succeed <<~EOM - D\t/cookbooks/x/metadata.json - D\t/cookbooks/y/metadata.json - EOM - end - end - end - - when_the_repository "is empty" do - it "knife upload does nothing" do - knife("upload /").should_succeed "" - knife("diff --name-status /").should_succeed <<~EOM - D\t/clients - D\t/cookbooks - D\t/data_bags - D\t/environments - D\t/nodes - D\t/roles - D\t/users - EOM - end - - it "knife upload --purge deletes nothing" do - knife("upload --purge /").should_fail <<~EOM - ERROR: /clients cannot be deleted. - ERROR: /cookbooks cannot be deleted. - ERROR: /data_bags cannot be deleted. - ERROR: /environments cannot be deleted. - ERROR: /nodes cannot be deleted. - ERROR: /roles cannot be deleted. - ERROR: /users cannot be deleted. - EOM - knife("diff --name-status /").should_succeed <<~EOM - D\t/clients - D\t/cookbooks - D\t/data_bags - D\t/environments - D\t/nodes - D\t/roles - D\t/users - EOM - end - - context "when current directory is top level" do - before do - cwd "." - end - - it "knife upload with no parameters reports an error" do - knife("upload").should_fail "FATAL: You must specify at least one argument. If you want to upload everything in this directory, run \"knife upload .\"\n", stdout: /USAGE/ - end - end - end - end - - when_the_chef_server "is empty" do - when_the_repository "has a data bag item" do - - before do - file "data_bags/x/y.json", { "foo" => "bar" } - end - - it "knife upload of the data bag uploads only the values in the data bag item and no other" do - knife("upload /data_bags/x/y.json").should_succeed <<~EOM - Created /data_bags/x - Created /data_bags/x/y.json - EOM - knife("diff --name-status /data_bags").should_succeed <<~EOM - EOM - expect(Chef::JSONCompat.parse(knife("raw /data/x/y").stdout, create_additions: false).keys.sort).to eq(%w{foo id}) - end - - it "knife upload /data_bags/x /data_bags/x/y.json uploads x once" do - knife("upload /data_bags/x /data_bags/x/y.json").should_succeed <<~EOM - Created /data_bags/x - Created /data_bags/x/y.json - EOM - end - end - - when_the_repository "has a data bag item with keys chef_type and data_bag" do - - before do - file "data_bags/x/y.json", { "chef_type" => "aaa", "data_bag" => "bbb" } - end - - it "upload preserves chef_type and data_bag" do - knife("upload /data_bags/x/y.json").should_succeed <<~EOM - Created /data_bags/x - Created /data_bags/x/y.json - EOM - knife("diff --name-status /data_bags").should_succeed "" - result = Chef::JSONCompat.parse(knife("raw /data/x/y").stdout, create_additions: false) - expect(result.keys.sort).to eq(%w{chef_type data_bag id}) - expect(result["chef_type"]).to eq("aaa") - expect(result["data_bag"]).to eq("bbb") - end - end - - # Test upload of an item when the other end doesn't even have the container - when_the_repository "has two data bag items" do - before do - file "data_bags/x/y.json", {} - file "data_bags/x/z.json", {} - end - it "knife upload of one data bag item itself succeeds" do - knife("upload /data_bags/x/y.json").should_succeed <<~EOM - Created /data_bags/x - Created /data_bags/x/y.json - EOM - knife("diff --name-status /data_bags").should_succeed <<~EOM - A\t/data_bags/x/z.json - EOM - end - end - end - - when_the_chef_server "has three data bag items" do - - before do - data_bag "x", { "deleted" => {}, "modified" => {}, "unmodified" => {} } - end - - when_the_repository "has a modified, unmodified, added and deleted data bag item" do - before do - file "data_bags/x/added.json", {} - file "data_bags/x/modified.json", { "foo" => "bar" } - file "data_bags/x/unmodified.json", {} - end - - it "knife upload of the modified file succeeds" do - knife("upload /data_bags/x/modified.json").should_succeed <<~EOM - Updated /data_bags/x/modified.json - EOM - knife("diff --name-status /data_bags").should_succeed <<~EOM - D\t/data_bags/x/deleted.json - A\t/data_bags/x/added.json - EOM - end - it "knife upload of the unmodified file does nothing" do - knife("upload /data_bags/x/unmodified.json").should_succeed "" - knife("diff --name-status /data_bags").should_succeed <<~EOM - D\t/data_bags/x/deleted.json - M\t/data_bags/x/modified.json - A\t/data_bags/x/added.json - EOM - end - it "knife upload of the added file succeeds" do - knife("upload /data_bags/x/added.json").should_succeed <<~EOM - Created /data_bags/x/added.json - EOM - knife("diff --name-status /data_bags").should_succeed <<~EOM - D\t/data_bags/x/deleted.json - M\t/data_bags/x/modified.json - EOM - end - it "knife upload of the deleted file does nothing" do - knife("upload /data_bags/x/deleted.json").should_succeed "" - knife("diff --name-status /data_bags").should_succeed <<~EOM - D\t/data_bags/x/deleted.json - M\t/data_bags/x/modified.json - A\t/data_bags/x/added.json - EOM - end - it "knife upload --purge of the deleted file deletes it" do - knife("upload --purge /data_bags/x/deleted.json").should_succeed <<~EOM - Deleted extra entry /data_bags/x/deleted.json (purge is on) - EOM - knife("diff --name-status /data_bags").should_succeed <<~EOM - M\t/data_bags/x/modified.json - A\t/data_bags/x/added.json - EOM - end - it "knife upload of the entire data bag uploads everything" do - knife("upload /data_bags/x").should_succeed <<~EOM - Created /data_bags/x/added.json - Updated /data_bags/x/modified.json - EOM - knife("diff --name-status /data_bags").should_succeed <<~EOM - D\t/data_bags/x/deleted.json - EOM - end - it "knife upload --purge of the entire data bag uploads everything" do - knife("upload --purge /data_bags/x").should_succeed <<~EOM - Created /data_bags/x/added.json - Updated /data_bags/x/modified.json - Deleted extra entry /data_bags/x/deleted.json (purge is on) - EOM - knife("diff --name-status /data_bags").should_succeed "" - end - context "when cwd is the /data_bags directory" do - - before do - cwd "data_bags" - end - - it "knife upload fails" do - knife("upload").should_fail "FATAL: You must specify at least one argument. If you want to upload everything in this directory, run \"knife upload .\"\n", stdout: /USAGE/ - end - - it "knife upload --purge . uploads everything" do - knife("upload --purge .").should_succeed <<~EOM - Created x/added.json - Updated x/modified.json - Deleted extra entry x/deleted.json (purge is on) - EOM - knife("diff --name-status /data_bags").should_succeed "" - end - it "knife upload --purge * uploads everything" do - knife("upload --purge *").should_succeed <<~EOM - Created x/added.json - Updated x/modified.json - Deleted extra entry x/deleted.json (purge is on) - EOM - knife("diff --name-status /data_bags").should_succeed "" - end - end - end - end - - # Cookbook upload is a funny thing ... direct cookbook upload works, but - # upload of a file is designed not to work at present. Make sure that is the - # case. - when_the_chef_server "has a cookbook" do - before do - cookbook "x", "1.0.0", { "z.rb" => "" } - end - - when_the_repository "does not have metadata file" do - before do - file "cookbooks/x/y.rb", "hi" - end - - it "raises MetadataNotFound exception" do - expect { knife("upload /cookbooks/x") }.to raise_error(Chef::Exceptions::MetadataNotFound) - end - end - - when_the_repository "does not have valid metadata" do - before do - file "cookbooks/x/metadata.rb", cb_metadata(nil, "1.0.0") - end - - it "raises exception for invalid metadata" do - expect { knife("upload /cookbooks/x") }.to raise_error(Chef::Exceptions::MetadataNotValid) - end - end - - when_the_repository "has a modified, extra and missing file for the cookbook" do - before do - file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0", "#modified") - file "cookbooks/x/y.rb", "hi" - end - - it "knife upload of any individual file fails" do - knife("upload /cookbooks/x/metadata.rb").should_fail "ERROR: /cookbooks/x/metadata.rb cannot be updated.\n" - knife("upload /cookbooks/x/y.rb").should_fail "ERROR: /cookbooks/x cannot have a child created under it.\n" - knife("upload --purge /cookbooks/x/z.rb").should_fail "ERROR: /cookbooks/x/z.rb cannot be deleted.\n" - end - - # TODO this is a bit of an inconsistency: if we didn't specify --purge, - # technically we shouldn't have deleted missing files. But ... cookbooks - # are a special case. - it "knife upload of the cookbook itself succeeds" do - knife("upload /cookbooks/x").should_succeed <<~EOM - Updated /cookbooks/x - EOM - knife("diff --name-status /cookbooks").should_succeed <<~EOM - D\t/cookbooks/x/metadata.json - EOM - end - - it "knife upload --purge of the cookbook itself succeeds" do - knife("upload /cookbooks/x").should_succeed <<~EOM - Updated /cookbooks/x - EOM - knife("diff --name-status /cookbooks").should_succeed <<~EOM - D\t/cookbooks/x/metadata.json - EOM - end - end - when_the_repository "has a missing file for the cookbook" do - - before do - file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0") - end - - it "knife upload of the cookbook succeeds" do - knife("upload /cookbooks/x").should_succeed <<~EOM - Updated /cookbooks/x - EOM - knife("diff --name-status /cookbooks").should_succeed <<~EOM - D\t/cookbooks/x/metadata.json - EOM - end - end - when_the_repository "has an extra file for the cookbook" do - - before do - file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0") - file "cookbooks/x/z.rb", "" - file "cookbooks/x/blah.rb", "" - end - - it "knife upload of the cookbook succeeds" do - knife("upload /cookbooks/x").should_succeed <<~EOM - Updated /cookbooks/x - EOM - knife("diff --name-status /cookbooks").should_succeed <<~EOM - D\t/cookbooks/x/metadata.json - EOM - end - end - - when_the_repository "has a different file in the cookbook" do - before do - file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0") - end - - it "knife upload --freeze freezes the cookbook" do - knife("upload --freeze /cookbooks/x").should_succeed <<~EOM - Updated /cookbooks/x - EOM - # Modify a file and attempt to upload - file "cookbooks/x/metadata.rb", 'name "x"; version "1.0.0"#different' - knife("upload /cookbooks/x").should_fail "ERROR: /cookbooks failed to write: Cookbook x is frozen\n" - end - end - end - - when_the_chef_server "has a frozen cookbook" do - before do - cookbook "frozencook", "1.0.0", {}, frozen: true - end - - when_the_repository "has an update to said cookbook" do - - before do - file "cookbooks/frozencook/metadata.rb", cb_metadata("frozencook", "1.0.0", "# This is different") - end - - it "knife upload fails to upload the frozen cookbook" do - knife("upload /cookbooks/frozencook").should_fail "ERROR: /cookbooks failed to write: Cookbook frozencook is frozen\n" - end - it "knife upload --force uploads the frozen cookbook" do - knife("upload --force /cookbooks/frozencook").should_succeed <<~EOM - Updated /cookbooks/frozencook - EOM - end - end - end - - when_the_repository "has a cookbook" do - before do - file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0") - file "cookbooks/x/metadata.json", { name: "x", version: "1.0.0" } - file "cookbooks/x/onlyin1.0.0.rb", "old_text" - end - - when_the_chef_server "has a later version for the cookbook" do - before do - cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" } - cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" } - end - - it "knife upload /cookbooks/x uploads the local version" do - knife("diff --name-status /cookbooks").should_succeed <<~EOM - M\t/cookbooks/x/metadata.rb - D\t/cookbooks/x/onlyin1.0.1.rb - A\t/cookbooks/x/metadata.json - A\t/cookbooks/x/onlyin1.0.0.rb - EOM - knife("upload --purge /cookbooks/x").should_succeed <<~EOM - Updated /cookbooks/x - EOM - knife("diff --name-status /cookbooks").should_succeed <<~EOM - M\t/cookbooks/x/metadata.rb - D\t/cookbooks/x/onlyin1.0.1.rb - A\t/cookbooks/x/metadata.json - A\t/cookbooks/x/onlyin1.0.0.rb - EOM - end - end - end - - when_the_repository "has a cookbook" do - before do - file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0") - file "cookbooks/x/onlyin1.0.0.rb", "old_text" - end - - when_the_chef_server "has a later version for the cookbook" do - before do - cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" } - cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" } - end - - it "knife upload /cookbooks/x uploads the local version and generates metadata.json from metadata.rb and uploads it." do - knife("diff --name-status /cookbooks").should_succeed <<~EOM - M\t/cookbooks/x/metadata.rb - D\t/cookbooks/x/onlyin1.0.1.rb - A\t/cookbooks/x/onlyin1.0.0.rb - EOM - knife("upload --purge /cookbooks/x").should_succeed <<~EOM - Updated /cookbooks/x - EOM - knife("diff --name-status /cookbooks").should_succeed <<~EOM - M\t/cookbooks/x/metadata.rb - D\t/cookbooks/x/onlyin1.0.1.rb - A\t/cookbooks/x/onlyin1.0.0.rb - EOM - end - end - - when_the_chef_server "has an earlier version for the cookbook" do - before do - cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" } - cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" } - end - - it "knife upload /cookbooks/x uploads the local version generates metadata.json and uploads it." do - knife("upload --purge /cookbooks/x").should_succeed <<~EOM - Updated /cookbooks/x - EOM - knife("diff --name-status /cookbooks").should_succeed <<~EOM - D\t/cookbooks/x/metadata.json - EOM - end - end - - when_the_chef_server "has a later version for the cookbook, and no current version" do - before do - cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" } - end - - it "knife upload /cookbooks/x uploads the local version and generates metadata.json before upload and uploads it." do - knife("diff --name-status /cookbooks").should_succeed <<~EOM - M\t/cookbooks/x/metadata.rb - D\t/cookbooks/x/onlyin1.0.1.rb - A\t/cookbooks/x/onlyin1.0.0.rb - EOM - knife("upload --purge /cookbooks/x").should_succeed <<~EOM - Updated /cookbooks/x - EOM - knife("diff --name-status /cookbooks").should_succeed <<~EOM - M\t/cookbooks/x/metadata.rb - D\t/cookbooks/x/onlyin1.0.1.rb - A\t/cookbooks/x/onlyin1.0.0.rb - EOM - end - end - - when_the_chef_server "has an earlier version for the cookbook, and no current version" do - before do - cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" } - end - - it "knife upload /cookbooks/x uploads the new version" do - knife("upload --purge /cookbooks/x").should_succeed <<~EOM - Updated /cookbooks/x - EOM - knife("diff --name-status /cookbooks").should_succeed <<~EOM - D\t/cookbooks/x/metadata.json - EOM - end - end - end - - when_the_chef_server "has an environment" do - before do - environment "x", {} - end - - when_the_repository "has an environment with bad JSON" do - before do - file "environments/x.json", "{" - end - - it "knife upload tries and fails" do - error1 = <<~EOH - WARN: Parse error reading #{path_to("environments/x.json")} as JSON: parse error: premature EOF - { - (right here) ------^ - - ERROR: /environments/x.json failed to write: Parse error reading JSON: parse error: premature EOF - { - (right here) ------^ - EOH - - warn = <<~EOH - WARN: Parse error reading #{path_to("environments/x.json")} as JSON: parse error: premature EOF - { - (right here) ------^ - - EOH - knife("upload /environments/x.json").should_fail(error1) - knife("diff --name-status /environments/x.json").should_succeed("M\t/environments/x.json\n", stderr: warn) - end - end - - when_the_repository "has the same environment with the wrong name in the file" do - before do - file "environments/x.json", { "name" => "y" } - end - it "knife upload fails" do - knife("upload /environments/x.json").should_fail "ERROR: /environments/x.json failed to write: Name must be 'x' (is 'y')\n" - knife("diff --name-status /environments/x.json").should_succeed "M\t/environments/x.json\n" - end - end - - when_the_repository "has the same environment with no name in the file" do - before do - file "environments/x.json", { "description" => "hi" } - end - it "knife upload succeeds" do - knife("upload /environments/x.json").should_succeed "Updated /environments/x.json\n" - knife("diff --name-status /environments/x.json").should_succeed "" - end - end - end - - when_the_chef_server "is empty" do - - when_the_repository "has an environment with the wrong name in the file" do - before do - file "environments/x.json", { "name" => "y" } - end - it "knife upload fails" do - knife("upload /environments/x.json").should_fail "ERROR: /environments failed to create_child: Error creating 'x.json': Name must be 'x' (is 'y')\n" - knife("diff --name-status /environments/x.json").should_succeed "A\t/environments/x.json\n" - end - end - - when_the_repository "has an environment with no name in the file" do - - before do - file "environments/x.json", { "description" => "hi" } - end - it "knife upload succeeds" do - knife("upload /environments/x.json").should_succeed "Created /environments/x.json\n" - knife("diff --name-status /environments/x.json").should_succeed "" - end - end - - when_the_repository "has a data bag with no id in the file" do - before do - file "data_bags/bag/x.json", { "foo" => "bar" } - end - it "knife upload succeeds" do - knife("upload /data_bags/bag/x.json").should_succeed "Created /data_bags/bag\nCreated /data_bags/bag/x.json\n" - knife("diff --name-status /data_bags/bag/x.json").should_succeed "" - end - end - end - when_the_chef_server "is empty" do - when_the_repository "has a cookbook with an invalid chef_version constraint in it" do - before do - file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0", "\nchef_version '~> 999.0'") - end - it "knife upload succeeds" do - knife("upload /cookbooks/x").should_succeed <<~EOM - Created /cookbooks/x - EOM - knife("diff --name-status /cookbooks").should_succeed <<~EOM - D\t/cookbooks/x/metadata.json - EOM - end - end - end - end # without versioned cookbooks - - context "with versioned cookbooks" do - before { Chef::Config[:versioned_cookbooks] = true } - - when_the_chef_server "has one of each thing" do - - before do - client "x", {} - cookbook "x", "1.0.0" - data_bag "x", { "y" => {} } - environment "x", {} - node "x", {} - role "x", {} - user "x", {} - end - - when_the_repository "has only top-level directories" do - before do - directory "clients" - directory "cookbooks" - directory "data_bags" - directory "environments" - directory "nodes" - directory "roles" - directory "users" - end - - it "knife upload does nothing" do - knife("upload /").should_succeed "" - knife("diff --name-status /").should_succeed <<~EOM - D\t/clients/chef-validator.json - D\t/clients/chef-webui.json - D\t/clients/x.json - D\t/cookbooks/x-1.0.0 - D\t/data_bags/x - D\t/environments/_default.json - D\t/environments/x.json - D\t/nodes/x.json - D\t/roles/x.json - D\t/users/admin.json - D\t/users/x.json - EOM - end - - it "knife upload --purge deletes everything" do - knife("upload --purge /").should_succeed(<<~EOM, stderr: "WARNING: /environments/_default.json cannot be deleted (default environment cannot be modified).\n") - Deleted extra entry /clients/chef-validator.json (purge is on) - Deleted extra entry /clients/chef-webui.json (purge is on) - Deleted extra entry /clients/x.json (purge is on) - Deleted extra entry /cookbooks/x-1.0.0 (purge is on) - Deleted extra entry /data_bags/x (purge is on) - Deleted extra entry /environments/x.json (purge is on) - Deleted extra entry /nodes/x.json (purge is on) - Deleted extra entry /roles/x.json (purge is on) - Deleted extra entry /users/admin.json (purge is on) - Deleted extra entry /users/x.json (purge is on) - EOM - knife("diff --name-status /").should_succeed <<~EOM - D\t/environments/_default.json - EOM - end - end - - when_the_repository "has an identical copy of each thing" do - before do - file "clients/chef-validator.json", { "validator" => true, "public_key" => ChefZero::PUBLIC_KEY } - file "clients/chef-webui.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY } - file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY } - file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0") - file "data_bags/x/y.json", {} - file "environments/_default.json", { "description" => "The default Chef environment" } - file "environments/x.json", {} - file "nodes/x.json", { "normal" => { "tags" => [] } } - file "roles/x.json", {} - file "users/admin.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY } - file "users/x.json", { "public_key" => ChefZero::PUBLIC_KEY } - end - - it "knife upload makes no changes" do - knife("upload /cookbooks/x-1.0.0").should_succeed "" - knife("diff --name-status /").should_succeed "" - end - - it "knife upload --purge makes no changes" do - knife("upload --purge /").should_succeed "" - knife("diff --name-status /").should_succeed "" - end - - context "except the role file" do - before do - file "roles/x.json", { "description" => "blarghle" } - end - - it "knife upload changes the role" do - knife("upload /").should_succeed "Updated /roles/x.json\n" - knife("diff --name-status /").should_succeed "" - end - end - - context "except the role file is textually different, but not ACTUALLY different" do - - before do - file "roles/x.json", <<~EOM - { - "chef_type": "role", - "default_attributes": { - }, - "env_run_lists": { - }, - "json_class": "Chef::Role", - "name": "x", - "description": "", - "override_attributes": { - }, - "run_list": [ - - ] - } - EOM - end - - it "knife upload / does not change anything" do - knife("upload /").should_succeed "" - knife("diff --name-status /").should_succeed "" - end - end - - context "as well as one extra copy of each thing" do - before do - file "clients/y.json", { "public_key" => ChefZero::PUBLIC_KEY } - file "cookbooks/x-1.0.0/blah.rb", "" - file "cookbooks/x-2.0.0/metadata.rb", cb_metadata("x", "2.0.0") - file "cookbooks/y-1.0.0/metadata.rb", cb_metadata("y", "1.0.0") - file "data_bags/x/z.json", {} - file "data_bags/y/zz.json", {} - file "environments/y.json", {} - file "nodes/y.json", {} - file "roles/y.json", {} - file "users/y.json", { "public_key" => ChefZero::PUBLIC_KEY } - end - - it "knife upload adds the new files" do - knife("upload /").should_succeed <<~EOM - Created /clients/y.json - Updated /cookbooks/x-1.0.0 - Created /cookbooks/x-2.0.0 - Created /cookbooks/y-1.0.0 - Created /data_bags/x/z.json - Created /data_bags/y - Created /data_bags/y/zz.json - Created /environments/y.json - Created /nodes/y.json - Created /roles/y.json - Created /users/y.json - EOM - knife("diff --name-status /").should_succeed "" - end - end - end - - when_the_repository "is empty" do - it "knife upload does nothing" do - knife("upload /").should_succeed "" - knife("diff --name-status /").should_succeed <<~EOM - D\t/clients - D\t/cookbooks - D\t/data_bags - D\t/environments - D\t/nodes - D\t/roles - D\t/users - EOM - end - - it "knife upload --purge deletes nothing" do - knife("upload --purge /").should_fail <<~EOM - ERROR: /clients cannot be deleted. - ERROR: /cookbooks cannot be deleted. - ERROR: /data_bags cannot be deleted. - ERROR: /environments cannot be deleted. - ERROR: /nodes cannot be deleted. - ERROR: /roles cannot be deleted. - ERROR: /users cannot be deleted. - EOM - knife("diff --name-status /").should_succeed <<~EOM - D\t/clients - D\t/cookbooks - D\t/data_bags - D\t/environments - D\t/nodes - D\t/roles - D\t/users - EOM - end - - context "when current directory is top level" do - before do - cwd "." - end - it "knife upload with no parameters reports an error" do - knife("upload").should_fail "FATAL: You must specify at least one argument. If you want to upload everything in this directory, run \"knife upload .\"\n", stdout: /USAGE/ - end - end - end - end - - # Test upload of an item when the other end doesn't even have the container - when_the_chef_server "is empty" do - when_the_repository "has two data bag items" do - before do - file "data_bags/x/y.json", {} - file "data_bags/x/z.json", {} - end - - it "knife upload of one data bag item itself succeeds" do - knife("upload /data_bags/x/y.json").should_succeed <<~EOM - Created /data_bags/x - Created /data_bags/x/y.json - EOM - knife("diff --name-status /data_bags").should_succeed <<~EOM - A\t/data_bags/x/z.json - EOM - end - end - end - - when_the_chef_server "has three data bag items" do - before do - data_bag "x", { "deleted" => {}, "modified" => {}, "unmodified" => {} } - end - when_the_repository "has a modified, unmodified, added and deleted data bag item" do - before do - file "data_bags/x/added.json", {} - file "data_bags/x/modified.json", { "foo" => "bar" } - file "data_bags/x/unmodified.json", {} - end - - it "knife upload of the modified file succeeds" do - knife("upload /data_bags/x/modified.json").should_succeed <<~EOM - Updated /data_bags/x/modified.json - EOM - knife("diff --name-status /data_bags").should_succeed <<~EOM - D\t/data_bags/x/deleted.json - A\t/data_bags/x/added.json - EOM - end - it "knife upload of the unmodified file does nothing" do - knife("upload /data_bags/x/unmodified.json").should_succeed "" - knife("diff --name-status /data_bags").should_succeed <<~EOM - D\t/data_bags/x/deleted.json - M\t/data_bags/x/modified.json - A\t/data_bags/x/added.json - EOM - end - it "knife upload of the added file succeeds" do - knife("upload /data_bags/x/added.json").should_succeed <<~EOM - Created /data_bags/x/added.json - EOM - knife("diff --name-status /data_bags").should_succeed <<~EOM - D\t/data_bags/x/deleted.json - M\t/data_bags/x/modified.json - EOM - end - it "knife upload of the deleted file does nothing" do - knife("upload /data_bags/x/deleted.json").should_succeed "" - knife("diff --name-status /data_bags").should_succeed <<~EOM - D\t/data_bags/x/deleted.json - M\t/data_bags/x/modified.json - A\t/data_bags/x/added.json - EOM - end - it "knife upload --purge of the deleted file deletes it" do - knife("upload --purge /data_bags/x/deleted.json").should_succeed <<~EOM - Deleted extra entry /data_bags/x/deleted.json (purge is on) - EOM - knife("diff --name-status /data_bags").should_succeed <<~EOM - M\t/data_bags/x/modified.json - A\t/data_bags/x/added.json - EOM - end - it "knife upload of the entire data bag uploads everything" do - knife("upload /data_bags/x").should_succeed <<~EOM - Created /data_bags/x/added.json - Updated /data_bags/x/modified.json - EOM - knife("diff --name-status /data_bags").should_succeed <<~EOM - D\t/data_bags/x/deleted.json - EOM - end - it "knife upload --purge of the entire data bag uploads everything" do - knife("upload --purge /data_bags/x").should_succeed <<~EOM - Created /data_bags/x/added.json - Updated /data_bags/x/modified.json - Deleted extra entry /data_bags/x/deleted.json (purge is on) - EOM - knife("diff --name-status /data_bags").should_succeed "" - end - context "when cwd is the /data_bags directory" do - before do - cwd "data_bags" - end - it "knife upload fails" do - knife("upload").should_fail "FATAL: You must specify at least one argument. If you want to upload everything in this directory, run \"knife upload .\"\n", stdout: /USAGE/ - end - it "knife upload --purge . uploads everything" do - knife("upload --purge .").should_succeed <<~EOM - Created x/added.json - Updated x/modified.json - Deleted extra entry x/deleted.json (purge is on) - EOM - knife("diff --name-status /data_bags").should_succeed "" - end - it "knife upload --purge * uploads everything" do - knife("upload --purge *").should_succeed <<~EOM - Created x/added.json - Updated x/modified.json - Deleted extra entry x/deleted.json (purge is on) - EOM - knife("diff --name-status /data_bags").should_succeed "" - end - end - end - end - - # Cookbook upload is a funny thing ... direct cookbook upload works, but - # upload of a file is designed not to work at present. Make sure that is the - # case. - when_the_chef_server "has a cookbook" do - before do - cookbook "x", "1.0.0", { "z.rb" => "" } - end - - when_the_repository "has a modified, extra and missing file for the cookbook" do - before do - file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0", "#modified") - file "cookbooks/x-1.0.0/y.rb", "hi" - end - - it "knife upload of any individual file fails" do - knife("upload /cookbooks/x-1.0.0/metadata.rb").should_fail "ERROR: /cookbooks/x-1.0.0/metadata.rb cannot be updated.\n" - knife("upload /cookbooks/x-1.0.0/y.rb").should_fail "ERROR: /cookbooks/x-1.0.0 cannot have a child created under it.\n" - knife("upload --purge /cookbooks/x-1.0.0/z.rb").should_fail "ERROR: /cookbooks/x-1.0.0/z.rb cannot be deleted.\n" - end - - # TODO this is a bit of an inconsistency: if we didn't specify --purge, - # technically we shouldn't have deleted missing files. But ... cookbooks - # are a special case. - it "knife upload of the cookbook itself succeeds" do - knife("upload /cookbooks/x-1.0.0").should_succeed <<~EOM - Updated /cookbooks/x-1.0.0 - EOM - knife("diff --name-status /cookbooks").should_succeed "" - end - - it "knife upload --purge of the cookbook itself succeeds" do - knife("upload /cookbooks/x-1.0.0").should_succeed <<~EOM - Updated /cookbooks/x-1.0.0 - EOM - knife("diff --name-status /cookbooks").should_succeed "" - end - end - - when_the_repository "has a missing file for the cookbook" do - before do - file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0") - end - - it "knife upload of the cookbook succeeds" do - knife("upload /cookbooks/x-1.0.0").should_succeed <<~EOM - Updated /cookbooks/x-1.0.0 - EOM - knife("diff --name-status /cookbooks").should_succeed "" - end - end - - when_the_repository "has an extra file for the cookbook" do - before do - file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0") - file "cookbooks/x-1.0.0/z.rb", "" - file "cookbooks/x-1.0.0/blah.rb", "" - end - - it "knife upload of the cookbook succeeds" do - knife("upload /cookbooks/x-1.0.0").should_succeed <<~EOM - Updated /cookbooks/x-1.0.0 - EOM - knife("diff --name-status /cookbooks").should_succeed "" - end - end - end - - when_the_repository "has a cookbook" do - before do - file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0") - file "cookbooks/x-1.0.0/onlyin1.0.0.rb", "old_text" - end - - when_the_chef_server "has a later version for the cookbook" do - before do - cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" } - cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" } - end - - it "knife upload /cookbooks uploads the local version" do - knife("diff --name-status /cookbooks").should_succeed <<~EOM - M\t/cookbooks/x-1.0.0/onlyin1.0.0.rb - D\t/cookbooks/x-1.0.1 - EOM - knife("upload --purge /cookbooks").should_succeed <<~EOM - Updated /cookbooks/x-1.0.0 - Deleted extra entry /cookbooks/x-1.0.1 (purge is on) - EOM - knife("diff --name-status /cookbooks").should_succeed "" - end - end - - when_the_chef_server "has an earlier version for the cookbook" do - before do - cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" } - cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" } - end - it "knife upload /cookbooks uploads the local version" do - knife("upload --purge /cookbooks").should_succeed <<~EOM - Updated /cookbooks/x-1.0.0 - Deleted extra entry /cookbooks/x-0.9.9 (purge is on) - EOM - knife("diff --name-status /cookbooks").should_succeed "" - end - end - - when_the_chef_server "has a later version for the cookbook, and no current version" do - before do - cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" } - end - - it "knife upload /cookbooks/x uploads the local version" do - knife("diff --name-status /cookbooks").should_succeed <<~EOM - D\t/cookbooks/x-1.0.1 - A\t/cookbooks/x-1.0.0 - EOM - knife("upload --purge /cookbooks").should_succeed <<~EOM - Created /cookbooks/x-1.0.0 - Deleted extra entry /cookbooks/x-1.0.1 (purge is on) - EOM - knife("diff --name-status /cookbooks").should_succeed "" - end - end - - when_the_chef_server "has an earlier version for the cookbook, and no current version" do - before do - cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" } - end - - it "knife upload /cookbooks/x uploads the new version" do - knife("upload --purge /cookbooks").should_succeed <<~EOM - Created /cookbooks/x-1.0.0 - Deleted extra entry /cookbooks/x-0.9.9 (purge is on) - EOM - knife("diff --name-status /cookbooks").should_succeed "" - end - end - end - - when_the_chef_server "has an environment" do - before do - environment "x", {} - end - - when_the_repository "has the same environment with the wrong name in the file" do - before do - file "environments/x.json", { "name" => "y" } - end - it "knife upload fails" do - knife("upload /environments/x.json").should_fail "ERROR: /environments/x.json failed to write: Name must be 'x' (is 'y')\n" - knife("diff --name-status /environments/x.json").should_succeed "M\t/environments/x.json\n" - end - end - - when_the_repository "has the same environment with no name in the file" do - before do - file "environments/x.json", { "description" => "hi" } - end - it "knife upload succeeds" do - knife("upload /environments/x.json").should_succeed "Updated /environments/x.json\n" - knife("diff --name-status /environments/x.json").should_succeed "" - end - end - end - - when_the_chef_server "is empty" do - - when_the_repository "has an environment with the wrong name in the file" do - before do - file "environments/x.json", { "name" => "y" } - end - it "knife upload fails" do - knife("upload /environments/x.json").should_fail "ERROR: /environments failed to create_child: Error creating 'x.json': Name must be 'x' (is 'y')\n" - knife("diff --name-status /environments/x.json").should_succeed "A\t/environments/x.json\n" - end - end - - when_the_repository "has an environment with no name in the file" do - before do - file "environments/x.json", { "description" => "hi" } - end - it "knife upload succeeds" do - knife("upload /environments/x.json").should_succeed "Created /environments/x.json\n" - knife("diff --name-status /environments/x.json").should_succeed "" - end - end - - when_the_repository "has a data bag with no id in the file" do - before do - file "data_bags/bag/x.json", { "foo" => "bar" } - end - it "knife upload succeeds" do - knife("upload /data_bags/bag/x.json").should_succeed "Created /data_bags/bag\nCreated /data_bags/bag/x.json\n" - knife("diff --name-status /data_bags/bag/x.json").should_succeed "" - end - end - end - - when_the_chef_server "is empty" do - when_the_repository "has a cookbook with an invalid chef_version constraint in it" do - before do - file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0", "\nchef_version '~> 999.0'") - end - it "knife upload succeeds" do - knife("upload /cookbooks/x-1.0.0").should_succeed <<~EOM - Created /cookbooks/x-1.0.0 - EOM - knife("diff --name-status /cookbooks").should_succeed "" - end - end - end - end # with versioned cookbooks - - when_the_chef_server "has a user" do - before do - user "x", {} - end - - when_the_repository "has the same user with json_class in it" do - before do - file "users/x.json", { "admin" => true, "json_class" => "Chef::WebUIUser" } - end - it "knife upload /users/x.json succeeds" do - knife("upload /users/x.json").should_succeed "Updated /users/x.json\n" - 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 at least 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 "cookbook_artifacts/x-1x1/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 "org.json", { "full_name" => "wootles" } - file "nodes/x.json", { "normal" => { "tags" => [] } } - file "policies/x-1.0.0.json", { "policy_group_list" => [ "x" ] } - file "policies/blah-1.0.0.json", { "policy_group_list" => [ "x" ] } - 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 upload / uploads everything" do - knife("upload /").should_succeed <<~EOM - Updated /acls/groups/blah.json - Created /clients/x.json - Created /containers/x.json - Created /cookbook_artifacts/x-1x1 - 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 /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" ]) - knife("diff --name-status --diff-filter=AMT /").should_succeed "" - 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" - cookbook_artifact "x", "1x1", "metadata.rb" => cb_metadata("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 the cookbook artifact" do - before do - cookbook_artifact "x", "1x1", { "recipes" => { "default.rb" => "" } } - end - - it "should fail because cookbook_artifacts cannot be updated" do - knife("upload /cookbook_artifacts/x-1x1").should_fail <<~EOM - ERROR: /cookbook_artifacts/x-1x1 cannot be updated: cookbook artifacts are immutable once uploaded. - 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" => "" } } - cookbook_artifact "x", "1x1", { "metadata.rb" => cb_metadata("x", "1.0.0") } - 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_group_list" => [ "x" ] } - policy "y", "1.0.0", { "policy_group_list" => [ "x" ] } - policy_group "x", { - "policies" => { - "x" => { "revision_id" => "1.0.0" }, - "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 - 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 "" - expect(api.get("/")["full_name"]).to eq("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" - expect(api.get("/")["full_name"]).to eq("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", %w{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" - expect(api.get("association_requests").map { |a| a["username"] }).to eq(%w{foo foobar}) - expect(api.get("users").map { |a| a["user"]["username"] }).to eq([ "bar" ]) - end - end - - when_the_repository "wants to make foo, bar and foobar members" do - before do - file "members.json", %w{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" - expect(api.get("association_requests").map { |a| a["username"] }).to eq([ ]) - expect(api.get("users").map { |a| a["user"]["username"] }).to eq(%w{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 "" - 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 - 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", %w{foo bar} - end - - it "knife upload / does nothing" do - knife("upload /").should_succeed "" - expect(api.get("association_requests").map { |a| a["username"] }).to eq(%w{bar foo}) - expect(api.get("users").map { |a| a["user"]["username"] }).to eq([ ]) - 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", %w{foo bar} - end - - it "knife upload / does nothing" do - knife("upload /").should_succeed "" - expect(api.get("association_requests").map { |a| a["username"] }).to eq([ ]) - expect(api.get("users").map { |a| a["user"]["username"] }).to eq(%w{bar foo}) - end - end - end - end - end -end +# +# Author:: John Keiser (<jkeiser@chef.io>) +# Copyright:: Copyright (c) Chef Software 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 "knife_spec_helper" +require "support/shared/integration/integration_helper" +require "chef/knife/upload" +require "chef/knife/diff" +require "chef/knife/raw" +require "chef/json_compat" + +describe "knife upload", :workstation do + include IntegrationSupport + include KnifeSupport + + context "without versioned cookbooks" do + + when_the_chef_server "has one of each thing" do + + before do + client "x", {} + cookbook "x", "1.0.0" + data_bag "x", { "y" => {} } + environment "x", {} + node "x", {} + role "x", {} + user "x", {} + end + + when_the_repository "has only top-level directories" do + before do + directory "clients" + directory "cookbooks" + directory "data_bags" + directory "environments" + directory "nodes" + directory "roles" + directory "users" + end + + it "knife upload does nothing" do + knife("upload /").should_succeed "" + knife("diff --name-status /").should_succeed <<~EOM + D\t/clients/chef-validator.json + D\t/clients/chef-webui.json + D\t/clients/x.json + D\t/cookbooks/x + D\t/data_bags/x + D\t/environments/_default.json + D\t/environments/x.json + D\t/nodes/x.json + D\t/roles/x.json + D\t/users/admin.json + D\t/users/x.json + EOM + end + + it "knife upload --purge deletes everything" do + knife("upload --purge /").should_succeed(<<~EOM, stderr: "WARNING: /environments/_default.json cannot be deleted (default environment cannot be modified).\n") + Deleted extra entry /clients/chef-validator.json (purge is on) + Deleted extra entry /clients/chef-webui.json (purge is on) + Deleted extra entry /clients/x.json (purge is on) + Deleted extra entry /cookbooks/x (purge is on) + Deleted extra entry /data_bags/x (purge is on) + Deleted extra entry /environments/x.json (purge is on) + Deleted extra entry /nodes/x.json (purge is on) + Deleted extra entry /roles/x.json (purge is on) + Deleted extra entry /users/admin.json (purge is on) + Deleted extra entry /users/x.json (purge is on) + EOM + knife("diff --name-status /").should_succeed <<~EOM + D\t/environments/_default.json + EOM + end + end + + when_the_repository "has an identical copy of each thing" do + + before do + file "clients/chef-validator.json", { "validator" => true, "public_key" => ChefZero::PUBLIC_KEY } + file "clients/chef-webui.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY } + file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY } + file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0") + file "data_bags/x/y.json", {} + file "environments/_default.json", { "description" => "The default Chef environment" } + file "environments/x.json", {} + file "nodes/x.json", { "normal" => { "tags" => [] } } + file "roles/x.json", {} + file "users/admin.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY } + file "users/x.json", { "public_key" => ChefZero::PUBLIC_KEY } + end + + it "knife upload makes no changes" do + knife("upload /cookbooks/x").should_succeed "" + knife("diff --name-status /").should_succeed "" + end + + it "knife upload --purge makes no changes" do + knife("upload --purge /").should_succeed "" + knife("diff --name-status /").should_succeed "" + end + + context "except the role file" do + before do + file "roles/x.json", { "description" => "blarghle" } + end + + it "knife upload changes the role" do + knife("upload /").should_succeed "Updated /roles/x.json\n" + knife("diff --name-status /").should_succeed "" + end + it "knife upload --no-diff does not change the role" do + knife("upload --no-diff /").should_succeed "" + knife("diff --name-status /").should_succeed "M\t/roles/x.json\n" + end + end + + context "except the role file is textually different, but not ACTUALLY different" do + before do + file "roles/x.json", <<~EOM + { + "chef_type": "role", + "default_attributes": { + }, + "env_run_lists": { + }, + "json_class": "Chef::Role", + "name": "x", + "description": "", + "override_attributes": { + }, + "run_list": [ + + ] + } + EOM + end + + it "knife upload / does not change anything" do + knife("upload /").should_succeed "" + knife("diff --name-status /").should_succeed "" + end + end + + context "the role is in ruby" do + before do + file "roles/x.rb", <<~EOM + name "x" + description "blargle" + EOM + end + + it "knife upload changes the role" do + knife("upload /").should_succeed "Updated /roles/x.json\n" + knife("diff --name-status /").should_succeed "" + end + + it "knife upload --no-diff does not change the role" do + knife("upload --no-diff /").should_succeed "" + knife("diff --name-status /").should_succeed "M\t/roles/x.rb\n" + end + end + + context "when cookbook metadata has a self-dependency" do + before do + file "cookbooks/x/metadata.rb", "name 'x'; version '1.0.0'; depends 'x'" + end + + it "fails with RuntimeError" do + expect { knife("upload /cookbooks") }.to raise_error RuntimeError, /Cookbook depends on itself/ + end + end + + context "as well as one extra copy of each thing" do + before do + file "clients/y.json", { "public_key" => ChefZero::PUBLIC_KEY } + file "cookbooks/x/blah.rb", "" + file "cookbooks/y/metadata.rb", cb_metadata("y", "1.0.0") + file "data_bags/x/z.json", {} + file "data_bags/y/zz.json", {} + file "environments/y.json", {} + file "nodes/y.json", {} + file "roles/y.json", {} + file "users/y.json", { "public_key" => ChefZero::PUBLIC_KEY } + end + + it "knife upload adds the new files" do + knife("upload /").should_succeed <<~EOM + Created /clients/y.json + Updated /cookbooks/x + Created /cookbooks/y + Created /data_bags/x/z.json + Created /data_bags/y + Created /data_bags/y/zz.json + Created /environments/y.json + Created /nodes/y.json + Created /roles/y.json + Created /users/y.json + EOM + knife("diff --name-status /").should_succeed <<~EOM + D\t/cookbooks/x/metadata.json + D\t/cookbooks/y/metadata.json + EOM + end + + it "knife upload --no-diff adds the new files" do + knife("upload --no-diff /").should_succeed <<~EOM + Created /clients/y.json + Updated /cookbooks/x + Created /cookbooks/y + Created /data_bags/x/z.json + Created /data_bags/y + Created /data_bags/y/zz.json + Created /environments/y.json + Created /nodes/y.json + Created /roles/y.json + Created /users/y.json + EOM + knife("diff --name-status /").should_succeed <<~EOM + D\t/cookbooks/x/metadata.json + D\t/cookbooks/y/metadata.json + EOM + end + end + end + + when_the_repository "is empty" do + it "knife upload does nothing" do + knife("upload /").should_succeed "" + knife("diff --name-status /").should_succeed <<~EOM + D\t/clients + D\t/cookbooks + D\t/data_bags + D\t/environments + D\t/nodes + D\t/roles + D\t/users + EOM + end + + it "knife upload --purge deletes nothing" do + knife("upload --purge /").should_fail <<~EOM + ERROR: /clients cannot be deleted. + ERROR: /cookbooks cannot be deleted. + ERROR: /data_bags cannot be deleted. + ERROR: /environments cannot be deleted. + ERROR: /nodes cannot be deleted. + ERROR: /roles cannot be deleted. + ERROR: /users cannot be deleted. + EOM + knife("diff --name-status /").should_succeed <<~EOM + D\t/clients + D\t/cookbooks + D\t/data_bags + D\t/environments + D\t/nodes + D\t/roles + D\t/users + EOM + end + + context "when current directory is top level" do + before do + cwd "." + end + + it "knife upload with no parameters reports an error" do + knife("upload").should_fail "FATAL: You must specify at least one argument. If you want to upload everything in this directory, run \"knife upload .\"\n", stdout: /USAGE/ + end + end + end + end + + when_the_chef_server "is empty" do + when_the_repository "has a data bag item" do + + before do + file "data_bags/x/y.json", { "foo" => "bar" } + end + + it "knife upload of the data bag uploads only the values in the data bag item and no other" do + knife("upload /data_bags/x/y.json").should_succeed <<~EOM + Created /data_bags/x + Created /data_bags/x/y.json + EOM + knife("diff --name-status /data_bags").should_succeed <<~EOM + EOM + expect(Chef::JSONCompat.parse(knife("raw /data/x/y").stdout, create_additions: false).keys.sort).to eq(%w{foo id}) + end + + it "knife upload /data_bags/x /data_bags/x/y.json uploads x once" do + knife("upload /data_bags/x /data_bags/x/y.json").should_succeed <<~EOM + Created /data_bags/x + Created /data_bags/x/y.json + EOM + end + end + + when_the_repository "has a data bag item with keys chef_type and data_bag" do + + before do + file "data_bags/x/y.json", { "chef_type" => "aaa", "data_bag" => "bbb" } + end + + it "upload preserves chef_type and data_bag" do + knife("upload /data_bags/x/y.json").should_succeed <<~EOM + Created /data_bags/x + Created /data_bags/x/y.json + EOM + knife("diff --name-status /data_bags").should_succeed "" + result = Chef::JSONCompat.parse(knife("raw /data/x/y").stdout, create_additions: false) + expect(result.keys.sort).to eq(%w{chef_type data_bag id}) + expect(result["chef_type"]).to eq("aaa") + expect(result["data_bag"]).to eq("bbb") + end + end + + # Test upload of an item when the other end doesn't even have the container + when_the_repository "has two data bag items" do + before do + file "data_bags/x/y.json", {} + file "data_bags/x/z.json", {} + end + it "knife upload of one data bag item itself succeeds" do + knife("upload /data_bags/x/y.json").should_succeed <<~EOM + Created /data_bags/x + Created /data_bags/x/y.json + EOM + knife("diff --name-status /data_bags").should_succeed <<~EOM + A\t/data_bags/x/z.json + EOM + end + end + end + + when_the_chef_server "has three data bag items" do + + before do + data_bag "x", { "deleted" => {}, "modified" => {}, "unmodified" => {} } + end + + when_the_repository "has a modified, unmodified, added and deleted data bag item" do + before do + file "data_bags/x/added.json", {} + file "data_bags/x/modified.json", { "foo" => "bar" } + file "data_bags/x/unmodified.json", {} + end + + it "knife upload of the modified file succeeds" do + knife("upload /data_bags/x/modified.json").should_succeed <<~EOM + Updated /data_bags/x/modified.json + EOM + knife("diff --name-status /data_bags").should_succeed <<~EOM + D\t/data_bags/x/deleted.json + A\t/data_bags/x/added.json + EOM + end + it "knife upload of the unmodified file does nothing" do + knife("upload /data_bags/x/unmodified.json").should_succeed "" + knife("diff --name-status /data_bags").should_succeed <<~EOM + D\t/data_bags/x/deleted.json + M\t/data_bags/x/modified.json + A\t/data_bags/x/added.json + EOM + end + it "knife upload of the added file succeeds" do + knife("upload /data_bags/x/added.json").should_succeed <<~EOM + Created /data_bags/x/added.json + EOM + knife("diff --name-status /data_bags").should_succeed <<~EOM + D\t/data_bags/x/deleted.json + M\t/data_bags/x/modified.json + EOM + end + it "knife upload of the deleted file does nothing" do + knife("upload /data_bags/x/deleted.json").should_succeed "" + knife("diff --name-status /data_bags").should_succeed <<~EOM + D\t/data_bags/x/deleted.json + M\t/data_bags/x/modified.json + A\t/data_bags/x/added.json + EOM + end + it "knife upload --purge of the deleted file deletes it" do + knife("upload --purge /data_bags/x/deleted.json").should_succeed <<~EOM + Deleted extra entry /data_bags/x/deleted.json (purge is on) + EOM + knife("diff --name-status /data_bags").should_succeed <<~EOM + M\t/data_bags/x/modified.json + A\t/data_bags/x/added.json + EOM + end + it "knife upload of the entire data bag uploads everything" do + knife("upload /data_bags/x").should_succeed <<~EOM + Created /data_bags/x/added.json + Updated /data_bags/x/modified.json + EOM + knife("diff --name-status /data_bags").should_succeed <<~EOM + D\t/data_bags/x/deleted.json + EOM + end + it "knife upload --purge of the entire data bag uploads everything" do + knife("upload --purge /data_bags/x").should_succeed <<~EOM + Created /data_bags/x/added.json + Updated /data_bags/x/modified.json + Deleted extra entry /data_bags/x/deleted.json (purge is on) + EOM + knife("diff --name-status /data_bags").should_succeed "" + end + context "when cwd is the /data_bags directory" do + + before do + cwd "data_bags" + end + + it "knife upload fails" do + knife("upload").should_fail "FATAL: You must specify at least one argument. If you want to upload everything in this directory, run \"knife upload .\"\n", stdout: /USAGE/ + end + + it "knife upload --purge . uploads everything" do + knife("upload --purge .").should_succeed <<~EOM + Created x/added.json + Updated x/modified.json + Deleted extra entry x/deleted.json (purge is on) + EOM + knife("diff --name-status /data_bags").should_succeed "" + end + it "knife upload --purge * uploads everything" do + knife("upload --purge *").should_succeed <<~EOM + Created x/added.json + Updated x/modified.json + Deleted extra entry x/deleted.json (purge is on) + EOM + knife("diff --name-status /data_bags").should_succeed "" + end + end + end + end + + # Cookbook upload is a funny thing ... direct cookbook upload works, but + # upload of a file is designed not to work at present. Make sure that is the + # case. + when_the_chef_server "has a cookbook" do + before do + cookbook "x", "1.0.0", { "z.rb" => "" } + end + + when_the_repository "does not have metadata file" do + before do + file "cookbooks/x/y.rb", "hi" + end + + it "raises MetadataNotFound exception" do + expect { knife("upload /cookbooks/x") }.to raise_error(Chef::Exceptions::MetadataNotFound) + end + end + + when_the_repository "does not have valid metadata" do + before do + file "cookbooks/x/metadata.rb", cb_metadata(nil, "1.0.0") + end + + it "raises exception for invalid metadata" do + expect { knife("upload /cookbooks/x") }.to raise_error(Chef::Exceptions::MetadataNotValid) + end + end + + when_the_repository "has a modified, extra and missing file for the cookbook" do + before do + file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0", "#modified") + file "cookbooks/x/y.rb", "hi" + end + + it "knife upload of any individual file fails" do + knife("upload /cookbooks/x/metadata.rb").should_fail "ERROR: /cookbooks/x/metadata.rb cannot be updated.\n" + knife("upload /cookbooks/x/y.rb").should_fail "ERROR: /cookbooks/x cannot have a child created under it.\n" + knife("upload --purge /cookbooks/x/z.rb").should_fail "ERROR: /cookbooks/x/z.rb cannot be deleted.\n" + end + + # TODO this is a bit of an inconsistency: if we didn't specify --purge, + # technically we shouldn't have deleted missing files. But ... cookbooks + # are a special case. + it "knife upload of the cookbook itself succeeds" do + knife("upload /cookbooks/x").should_succeed <<~EOM + Updated /cookbooks/x + EOM + knife("diff --name-status /cookbooks").should_succeed <<~EOM + D\t/cookbooks/x/metadata.json + EOM + end + + it "knife upload --purge of the cookbook itself succeeds" do + knife("upload /cookbooks/x").should_succeed <<~EOM + Updated /cookbooks/x + EOM + knife("diff --name-status /cookbooks").should_succeed <<~EOM + D\t/cookbooks/x/metadata.json + EOM + end + end + when_the_repository "has a missing file for the cookbook" do + + before do + file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0") + end + + it "knife upload of the cookbook succeeds" do + knife("upload /cookbooks/x").should_succeed <<~EOM + Updated /cookbooks/x + EOM + knife("diff --name-status /cookbooks").should_succeed <<~EOM + D\t/cookbooks/x/metadata.json + EOM + end + end + when_the_repository "has an extra file for the cookbook" do + + before do + file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0") + file "cookbooks/x/z.rb", "" + file "cookbooks/x/blah.rb", "" + end + + it "knife upload of the cookbook succeeds" do + knife("upload /cookbooks/x").should_succeed <<~EOM + Updated /cookbooks/x + EOM + knife("diff --name-status /cookbooks").should_succeed <<~EOM + D\t/cookbooks/x/metadata.json + EOM + end + end + + when_the_repository "has a different file in the cookbook" do + before do + file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0") + end + + it "knife upload --freeze freezes the cookbook" do + knife("upload --freeze /cookbooks/x").should_succeed <<~EOM + Updated /cookbooks/x + EOM + # Modify a file and attempt to upload + file "cookbooks/x/metadata.rb", 'name "x"; version "1.0.0"#different' + knife("upload /cookbooks/x").should_fail "ERROR: /cookbooks failed to write: Cookbook x is frozen\n" + end + end + end + + when_the_chef_server "has a frozen cookbook" do + before do + cookbook "frozencook", "1.0.0", {}, frozen: true + end + + when_the_repository "has an update to said cookbook" do + + before do + file "cookbooks/frozencook/metadata.rb", cb_metadata("frozencook", "1.0.0", "# This is different") + end + + it "knife upload fails to upload the frozen cookbook" do + knife("upload /cookbooks/frozencook").should_fail "ERROR: /cookbooks failed to write: Cookbook frozencook is frozen\n" + end + it "knife upload --force uploads the frozen cookbook" do + knife("upload --force /cookbooks/frozencook").should_succeed <<~EOM + Updated /cookbooks/frozencook + EOM + end + end + end + + when_the_repository "has a cookbook" do + before do + file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0") + file "cookbooks/x/metadata.json", { name: "x", version: "1.0.0" } + file "cookbooks/x/onlyin1.0.0.rb", "old_text" + end + + when_the_chef_server "has a later version for the cookbook" do + before do + cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" } + cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" } + end + + it "knife upload /cookbooks/x uploads the local version" do + knife("diff --name-status /cookbooks").should_succeed <<~EOM + M\t/cookbooks/x/metadata.rb + D\t/cookbooks/x/onlyin1.0.1.rb + A\t/cookbooks/x/metadata.json + A\t/cookbooks/x/onlyin1.0.0.rb + EOM + knife("upload --purge /cookbooks/x").should_succeed <<~EOM + Updated /cookbooks/x + EOM + knife("diff --name-status /cookbooks").should_succeed <<~EOM + M\t/cookbooks/x/metadata.rb + D\t/cookbooks/x/onlyin1.0.1.rb + A\t/cookbooks/x/metadata.json + A\t/cookbooks/x/onlyin1.0.0.rb + EOM + end + end + end + + when_the_repository "has a cookbook" do + before do + file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0") + file "cookbooks/x/onlyin1.0.0.rb", "old_text" + end + + when_the_chef_server "has a later version for the cookbook" do + before do + cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" } + cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" } + end + + it "knife upload /cookbooks/x uploads the local version and generates metadata.json from metadata.rb and uploads it." do + knife("diff --name-status /cookbooks").should_succeed <<~EOM + M\t/cookbooks/x/metadata.rb + D\t/cookbooks/x/onlyin1.0.1.rb + A\t/cookbooks/x/onlyin1.0.0.rb + EOM + knife("upload --purge /cookbooks/x").should_succeed <<~EOM + Updated /cookbooks/x + EOM + knife("diff --name-status /cookbooks").should_succeed <<~EOM + M\t/cookbooks/x/metadata.rb + D\t/cookbooks/x/onlyin1.0.1.rb + A\t/cookbooks/x/onlyin1.0.0.rb + EOM + end + end + + when_the_chef_server "has an earlier version for the cookbook" do + before do + cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" } + cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" } + end + + it "knife upload /cookbooks/x uploads the local version generates metadata.json and uploads it." do + knife("upload --purge /cookbooks/x").should_succeed <<~EOM + Updated /cookbooks/x + EOM + knife("diff --name-status /cookbooks").should_succeed <<~EOM + D\t/cookbooks/x/metadata.json + EOM + end + end + + when_the_chef_server "has a later version for the cookbook, and no current version" do + before do + cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" } + end + + it "knife upload /cookbooks/x uploads the local version and generates metadata.json before upload and uploads it." do + knife("diff --name-status /cookbooks").should_succeed <<~EOM + M\t/cookbooks/x/metadata.rb + D\t/cookbooks/x/onlyin1.0.1.rb + A\t/cookbooks/x/onlyin1.0.0.rb + EOM + knife("upload --purge /cookbooks/x").should_succeed <<~EOM + Updated /cookbooks/x + EOM + knife("diff --name-status /cookbooks").should_succeed <<~EOM + M\t/cookbooks/x/metadata.rb + D\t/cookbooks/x/onlyin1.0.1.rb + A\t/cookbooks/x/onlyin1.0.0.rb + EOM + end + end + + when_the_chef_server "has an earlier version for the cookbook, and no current version" do + before do + cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" } + end + + it "knife upload /cookbooks/x uploads the new version" do + knife("upload --purge /cookbooks/x").should_succeed <<~EOM + Updated /cookbooks/x + EOM + knife("diff --name-status /cookbooks").should_succeed <<~EOM + D\t/cookbooks/x/metadata.json + EOM + end + end + end + + when_the_chef_server "has an environment" do + before do + environment "x", {} + end + + when_the_repository "has an environment with bad JSON" do + before do + file "environments/x.json", "{" + end + + it "knife upload tries and fails" do + error1 = <<~EOH + WARN: Parse error reading #{path_to("environments/x.json")} as JSON: parse error: premature EOF + { + (right here) ------^ + + ERROR: /environments/x.json failed to write: Parse error reading JSON: parse error: premature EOF + { + (right here) ------^ + EOH + + warn = <<~EOH + WARN: Parse error reading #{path_to("environments/x.json")} as JSON: parse error: premature EOF + { + (right here) ------^ + + EOH + knife("upload /environments/x.json").should_fail(error1) + knife("diff --name-status /environments/x.json").should_succeed("M\t/environments/x.json\n", stderr: warn) + end + end + + when_the_repository "has the same environment with the wrong name in the file" do + before do + file "environments/x.json", { "name" => "y" } + end + it "knife upload fails" do + knife("upload /environments/x.json").should_fail "ERROR: /environments/x.json failed to write: Name must be 'x' (is 'y')\n" + knife("diff --name-status /environments/x.json").should_succeed "M\t/environments/x.json\n" + end + end + + when_the_repository "has the same environment with no name in the file" do + before do + file "environments/x.json", { "description" => "hi" } + end + it "knife upload succeeds" do + knife("upload /environments/x.json").should_succeed "Updated /environments/x.json\n" + knife("diff --name-status /environments/x.json").should_succeed "" + end + end + end + + when_the_chef_server "is empty" do + + when_the_repository "has an environment with the wrong name in the file" do + before do + file "environments/x.json", { "name" => "y" } + end + it "knife upload fails" do + knife("upload /environments/x.json").should_fail "ERROR: /environments failed to create_child: Error creating 'x.json': Name must be 'x' (is 'y')\n" + knife("diff --name-status /environments/x.json").should_succeed "A\t/environments/x.json\n" + end + end + + when_the_repository "has an environment with no name in the file" do + + before do + file "environments/x.json", { "description" => "hi" } + end + it "knife upload succeeds" do + knife("upload /environments/x.json").should_succeed "Created /environments/x.json\n" + knife("diff --name-status /environments/x.json").should_succeed "" + end + end + + when_the_repository "has a data bag with no id in the file" do + before do + file "data_bags/bag/x.json", { "foo" => "bar" } + end + it "knife upload succeeds" do + knife("upload /data_bags/bag/x.json").should_succeed "Created /data_bags/bag\nCreated /data_bags/bag/x.json\n" + knife("diff --name-status /data_bags/bag/x.json").should_succeed "" + end + end + end + when_the_chef_server "is empty" do + when_the_repository "has a cookbook with an invalid chef_version constraint in it" do + before do + file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0", "\nchef_version '~> 999.0'") + end + it "knife upload succeeds" do + knife("upload /cookbooks/x").should_succeed <<~EOM + Created /cookbooks/x + EOM + knife("diff --name-status /cookbooks").should_succeed <<~EOM + D\t/cookbooks/x/metadata.json + EOM + end + end + end + end # without versioned cookbooks + + context "with versioned cookbooks" do + before { Chef::Config[:versioned_cookbooks] = true } + + when_the_chef_server "has one of each thing" do + + before do + client "x", {} + cookbook "x", "1.0.0" + data_bag "x", { "y" => {} } + environment "x", {} + node "x", {} + role "x", {} + user "x", {} + end + + when_the_repository "has only top-level directories" do + before do + directory "clients" + directory "cookbooks" + directory "data_bags" + directory "environments" + directory "nodes" + directory "roles" + directory "users" + end + + it "knife upload does nothing" do + knife("upload /").should_succeed "" + knife("diff --name-status /").should_succeed <<~EOM + D\t/clients/chef-validator.json + D\t/clients/chef-webui.json + D\t/clients/x.json + D\t/cookbooks/x-1.0.0 + D\t/data_bags/x + D\t/environments/_default.json + D\t/environments/x.json + D\t/nodes/x.json + D\t/roles/x.json + D\t/users/admin.json + D\t/users/x.json + EOM + end + + it "knife upload --purge deletes everything" do + knife("upload --purge /").should_succeed(<<~EOM, stderr: "WARNING: /environments/_default.json cannot be deleted (default environment cannot be modified).\n") + Deleted extra entry /clients/chef-validator.json (purge is on) + Deleted extra entry /clients/chef-webui.json (purge is on) + Deleted extra entry /clients/x.json (purge is on) + Deleted extra entry /cookbooks/x-1.0.0 (purge is on) + Deleted extra entry /data_bags/x (purge is on) + Deleted extra entry /environments/x.json (purge is on) + Deleted extra entry /nodes/x.json (purge is on) + Deleted extra entry /roles/x.json (purge is on) + Deleted extra entry /users/admin.json (purge is on) + Deleted extra entry /users/x.json (purge is on) + EOM + knife("diff --name-status /").should_succeed <<~EOM + D\t/environments/_default.json + EOM + end + end + + when_the_repository "has an identical copy of each thing" do + before do + file "clients/chef-validator.json", { "validator" => true, "public_key" => ChefZero::PUBLIC_KEY } + file "clients/chef-webui.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY } + file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY } + file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0") + file "data_bags/x/y.json", {} + file "environments/_default.json", { "description" => "The default Chef environment" } + file "environments/x.json", {} + file "nodes/x.json", { "normal" => { "tags" => [] } } + file "roles/x.json", {} + file "users/admin.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY } + file "users/x.json", { "public_key" => ChefZero::PUBLIC_KEY } + end + + it "knife upload makes no changes" do + knife("upload /cookbooks/x-1.0.0").should_succeed "" + knife("diff --name-status /").should_succeed "" + end + + it "knife upload --purge makes no changes" do + knife("upload --purge /").should_succeed "" + knife("diff --name-status /").should_succeed "" + end + + context "except the role file" do + before do + file "roles/x.json", { "description" => "blarghle" } + end + + it "knife upload changes the role" do + knife("upload /").should_succeed "Updated /roles/x.json\n" + knife("diff --name-status /").should_succeed "" + end + end + + context "except the role file is textually different, but not ACTUALLY different" do + + before do + file "roles/x.json", <<~EOM + { + "chef_type": "role", + "default_attributes": { + }, + "env_run_lists": { + }, + "json_class": "Chef::Role", + "name": "x", + "description": "", + "override_attributes": { + }, + "run_list": [ + + ] + } + EOM + end + + it "knife upload / does not change anything" do + knife("upload /").should_succeed "" + knife("diff --name-status /").should_succeed "" + end + end + + context "as well as one extra copy of each thing" do + before do + file "clients/y.json", { "public_key" => ChefZero::PUBLIC_KEY } + file "cookbooks/x-1.0.0/blah.rb", "" + file "cookbooks/x-2.0.0/metadata.rb", cb_metadata("x", "2.0.0") + file "cookbooks/y-1.0.0/metadata.rb", cb_metadata("y", "1.0.0") + file "data_bags/x/z.json", {} + file "data_bags/y/zz.json", {} + file "environments/y.json", {} + file "nodes/y.json", {} + file "roles/y.json", {} + file "users/y.json", { "public_key" => ChefZero::PUBLIC_KEY } + end + + it "knife upload adds the new files" do + knife("upload /").should_succeed <<~EOM + Created /clients/y.json + Updated /cookbooks/x-1.0.0 + Created /cookbooks/x-2.0.0 + Created /cookbooks/y-1.0.0 + Created /data_bags/x/z.json + Created /data_bags/y + Created /data_bags/y/zz.json + Created /environments/y.json + Created /nodes/y.json + Created /roles/y.json + Created /users/y.json + EOM + knife("diff --name-status /").should_succeed "" + end + end + end + + when_the_repository "is empty" do + it "knife upload does nothing" do + knife("upload /").should_succeed "" + knife("diff --name-status /").should_succeed <<~EOM + D\t/clients + D\t/cookbooks + D\t/data_bags + D\t/environments + D\t/nodes + D\t/roles + D\t/users + EOM + end + + it "knife upload --purge deletes nothing" do + knife("upload --purge /").should_fail <<~EOM + ERROR: /clients cannot be deleted. + ERROR: /cookbooks cannot be deleted. + ERROR: /data_bags cannot be deleted. + ERROR: /environments cannot be deleted. + ERROR: /nodes cannot be deleted. + ERROR: /roles cannot be deleted. + ERROR: /users cannot be deleted. + EOM + knife("diff --name-status /").should_succeed <<~EOM + D\t/clients + D\t/cookbooks + D\t/data_bags + D\t/environments + D\t/nodes + D\t/roles + D\t/users + EOM + end + + context "when current directory is top level" do + before do + cwd "." + end + it "knife upload with no parameters reports an error" do + knife("upload").should_fail "FATAL: You must specify at least one argument. If you want to upload everything in this directory, run \"knife upload .\"\n", stdout: /USAGE/ + end + end + end + end + + # Test upload of an item when the other end doesn't even have the container + when_the_chef_server "is empty" do + when_the_repository "has two data bag items" do + before do + file "data_bags/x/y.json", {} + file "data_bags/x/z.json", {} + end + + it "knife upload of one data bag item itself succeeds" do + knife("upload /data_bags/x/y.json").should_succeed <<~EOM + Created /data_bags/x + Created /data_bags/x/y.json + EOM + knife("diff --name-status /data_bags").should_succeed <<~EOM + A\t/data_bags/x/z.json + EOM + end + end + end + + when_the_chef_server "has three data bag items" do + before do + data_bag "x", { "deleted" => {}, "modified" => {}, "unmodified" => {} } + end + when_the_repository "has a modified, unmodified, added and deleted data bag item" do + before do + file "data_bags/x/added.json", {} + file "data_bags/x/modified.json", { "foo" => "bar" } + file "data_bags/x/unmodified.json", {} + end + + it "knife upload of the modified file succeeds" do + knife("upload /data_bags/x/modified.json").should_succeed <<~EOM + Updated /data_bags/x/modified.json + EOM + knife("diff --name-status /data_bags").should_succeed <<~EOM + D\t/data_bags/x/deleted.json + A\t/data_bags/x/added.json + EOM + end + it "knife upload of the unmodified file does nothing" do + knife("upload /data_bags/x/unmodified.json").should_succeed "" + knife("diff --name-status /data_bags").should_succeed <<~EOM + D\t/data_bags/x/deleted.json + M\t/data_bags/x/modified.json + A\t/data_bags/x/added.json + EOM + end + it "knife upload of the added file succeeds" do + knife("upload /data_bags/x/added.json").should_succeed <<~EOM + Created /data_bags/x/added.json + EOM + knife("diff --name-status /data_bags").should_succeed <<~EOM + D\t/data_bags/x/deleted.json + M\t/data_bags/x/modified.json + EOM + end + it "knife upload of the deleted file does nothing" do + knife("upload /data_bags/x/deleted.json").should_succeed "" + knife("diff --name-status /data_bags").should_succeed <<~EOM + D\t/data_bags/x/deleted.json + M\t/data_bags/x/modified.json + A\t/data_bags/x/added.json + EOM + end + it "knife upload --purge of the deleted file deletes it" do + knife("upload --purge /data_bags/x/deleted.json").should_succeed <<~EOM + Deleted extra entry /data_bags/x/deleted.json (purge is on) + EOM + knife("diff --name-status /data_bags").should_succeed <<~EOM + M\t/data_bags/x/modified.json + A\t/data_bags/x/added.json + EOM + end + it "knife upload of the entire data bag uploads everything" do + knife("upload /data_bags/x").should_succeed <<~EOM + Created /data_bags/x/added.json + Updated /data_bags/x/modified.json + EOM + knife("diff --name-status /data_bags").should_succeed <<~EOM + D\t/data_bags/x/deleted.json + EOM + end + it "knife upload --purge of the entire data bag uploads everything" do + knife("upload --purge /data_bags/x").should_succeed <<~EOM + Created /data_bags/x/added.json + Updated /data_bags/x/modified.json + Deleted extra entry /data_bags/x/deleted.json (purge is on) + EOM + knife("diff --name-status /data_bags").should_succeed "" + end + context "when cwd is the /data_bags directory" do + before do + cwd "data_bags" + end + it "knife upload fails" do + knife("upload").should_fail "FATAL: You must specify at least one argument. If you want to upload everything in this directory, run \"knife upload .\"\n", stdout: /USAGE/ + end + it "knife upload --purge . uploads everything" do + knife("upload --purge .").should_succeed <<~EOM + Created x/added.json + Updated x/modified.json + Deleted extra entry x/deleted.json (purge is on) + EOM + knife("diff --name-status /data_bags").should_succeed "" + end + it "knife upload --purge * uploads everything" do + knife("upload --purge *").should_succeed <<~EOM + Created x/added.json + Updated x/modified.json + Deleted extra entry x/deleted.json (purge is on) + EOM + knife("diff --name-status /data_bags").should_succeed "" + end + end + end + end + + # Cookbook upload is a funny thing ... direct cookbook upload works, but + # upload of a file is designed not to work at present. Make sure that is the + # case. + when_the_chef_server "has a cookbook" do + before do + cookbook "x", "1.0.0", { "z.rb" => "" } + end + + when_the_repository "has a modified, extra and missing file for the cookbook" do + before do + file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0", "#modified") + file "cookbooks/x-1.0.0/y.rb", "hi" + end + + it "knife upload of any individual file fails" do + knife("upload /cookbooks/x-1.0.0/metadata.rb").should_fail "ERROR: /cookbooks/x-1.0.0/metadata.rb cannot be updated.\n" + knife("upload /cookbooks/x-1.0.0/y.rb").should_fail "ERROR: /cookbooks/x-1.0.0 cannot have a child created under it.\n" + knife("upload --purge /cookbooks/x-1.0.0/z.rb").should_fail "ERROR: /cookbooks/x-1.0.0/z.rb cannot be deleted.\n" + end + + # TODO this is a bit of an inconsistency: if we didn't specify --purge, + # technically we shouldn't have deleted missing files. But ... cookbooks + # are a special case. + it "knife upload of the cookbook itself succeeds" do + knife("upload /cookbooks/x-1.0.0").should_succeed <<~EOM + Updated /cookbooks/x-1.0.0 + EOM + knife("diff --name-status /cookbooks").should_succeed "" + end + + it "knife upload --purge of the cookbook itself succeeds" do + knife("upload /cookbooks/x-1.0.0").should_succeed <<~EOM + Updated /cookbooks/x-1.0.0 + EOM + knife("diff --name-status /cookbooks").should_succeed "" + end + end + + when_the_repository "has a missing file for the cookbook" do + before do + file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0") + end + + it "knife upload of the cookbook succeeds" do + knife("upload /cookbooks/x-1.0.0").should_succeed <<~EOM + Updated /cookbooks/x-1.0.0 + EOM + knife("diff --name-status /cookbooks").should_succeed "" + end + end + + when_the_repository "has an extra file for the cookbook" do + before do + file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0") + file "cookbooks/x-1.0.0/z.rb", "" + file "cookbooks/x-1.0.0/blah.rb", "" + end + + it "knife upload of the cookbook succeeds" do + knife("upload /cookbooks/x-1.0.0").should_succeed <<~EOM + Updated /cookbooks/x-1.0.0 + EOM + knife("diff --name-status /cookbooks").should_succeed "" + end + end + end + + when_the_repository "has a cookbook" do + before do + file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0") + file "cookbooks/x-1.0.0/onlyin1.0.0.rb", "old_text" + end + + when_the_chef_server "has a later version for the cookbook" do + before do + cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" } + cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" } + end + + it "knife upload /cookbooks uploads the local version" do + knife("diff --name-status /cookbooks").should_succeed <<~EOM + M\t/cookbooks/x-1.0.0/onlyin1.0.0.rb + D\t/cookbooks/x-1.0.1 + EOM + knife("upload --purge /cookbooks").should_succeed <<~EOM + Updated /cookbooks/x-1.0.0 + Deleted extra entry /cookbooks/x-1.0.1 (purge is on) + EOM + knife("diff --name-status /cookbooks").should_succeed "" + end + end + + when_the_chef_server "has an earlier version for the cookbook" do + before do + cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" } + cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" } + end + it "knife upload /cookbooks uploads the local version" do + knife("upload --purge /cookbooks").should_succeed <<~EOM + Updated /cookbooks/x-1.0.0 + Deleted extra entry /cookbooks/x-0.9.9 (purge is on) + EOM + knife("diff --name-status /cookbooks").should_succeed "" + end + end + + when_the_chef_server "has a later version for the cookbook, and no current version" do + before do + cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" } + end + + it "knife upload /cookbooks/x uploads the local version" do + knife("diff --name-status /cookbooks").should_succeed <<~EOM + D\t/cookbooks/x-1.0.1 + A\t/cookbooks/x-1.0.0 + EOM + knife("upload --purge /cookbooks").should_succeed <<~EOM + Created /cookbooks/x-1.0.0 + Deleted extra entry /cookbooks/x-1.0.1 (purge is on) + EOM + knife("diff --name-status /cookbooks").should_succeed "" + end + end + + when_the_chef_server "has an earlier version for the cookbook, and no current version" do + before do + cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" } + end + + it "knife upload /cookbooks/x uploads the new version" do + knife("upload --purge /cookbooks").should_succeed <<~EOM + Created /cookbooks/x-1.0.0 + Deleted extra entry /cookbooks/x-0.9.9 (purge is on) + EOM + knife("diff --name-status /cookbooks").should_succeed "" + end + end + end + + when_the_chef_server "has an environment" do + before do + environment "x", {} + end + + when_the_repository "has the same environment with the wrong name in the file" do + before do + file "environments/x.json", { "name" => "y" } + end + it "knife upload fails" do + knife("upload /environments/x.json").should_fail "ERROR: /environments/x.json failed to write: Name must be 'x' (is 'y')\n" + knife("diff --name-status /environments/x.json").should_succeed "M\t/environments/x.json\n" + end + end + + when_the_repository "has the same environment with no name in the file" do + before do + file "environments/x.json", { "description" => "hi" } + end + it "knife upload succeeds" do + knife("upload /environments/x.json").should_succeed "Updated /environments/x.json\n" + knife("diff --name-status /environments/x.json").should_succeed "" + end + end + end + + when_the_chef_server "is empty" do + + when_the_repository "has an environment with the wrong name in the file" do + before do + file "environments/x.json", { "name" => "y" } + end + it "knife upload fails" do + knife("upload /environments/x.json").should_fail "ERROR: /environments failed to create_child: Error creating 'x.json': Name must be 'x' (is 'y')\n" + knife("diff --name-status /environments/x.json").should_succeed "A\t/environments/x.json\n" + end + end + + when_the_repository "has an environment with no name in the file" do + before do + file "environments/x.json", { "description" => "hi" } + end + it "knife upload succeeds" do + knife("upload /environments/x.json").should_succeed "Created /environments/x.json\n" + knife("diff --name-status /environments/x.json").should_succeed "" + end + end + + when_the_repository "has a data bag with no id in the file" do + before do + file "data_bags/bag/x.json", { "foo" => "bar" } + end + it "knife upload succeeds" do + knife("upload /data_bags/bag/x.json").should_succeed "Created /data_bags/bag\nCreated /data_bags/bag/x.json\n" + knife("diff --name-status /data_bags/bag/x.json").should_succeed "" + end + end + end + + when_the_chef_server "is empty" do + when_the_repository "has a cookbook with an invalid chef_version constraint in it" do + before do + file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0", "\nchef_version '~> 999.0'") + end + it "knife upload succeeds" do + knife("upload /cookbooks/x-1.0.0").should_succeed <<~EOM + Created /cookbooks/x-1.0.0 + EOM + knife("diff --name-status /cookbooks").should_succeed "" + end + end + end + end # with versioned cookbooks + + when_the_chef_server "has a user" do + before do + user "x", {} + end + + when_the_repository "has the same user with json_class in it" do + before do + file "users/x.json", { "admin" => true, "json_class" => "Chef::WebUIUser" } + end + it "knife upload /users/x.json succeeds" do + knife("upload /users/x.json").should_succeed "Updated /users/x.json\n" + 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 at least 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 "cookbook_artifacts/x-1x1/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 "org.json", { "full_name" => "wootles" } + file "nodes/x.json", { "normal" => { "tags" => [] } } + file "policies/x-1.0.0.json", { "policy_group_list" => [ "x" ] } + file "policies/blah-1.0.0.json", { "policy_group_list" => [ "x" ] } + 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 upload / uploads everything" do + knife("upload /").should_succeed <<~EOM + Updated /acls/groups/blah.json + Created /clients/x.json + Created /containers/x.json + Created /cookbook_artifacts/x-1x1 + 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 /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" ]) + knife("diff --name-status --diff-filter=AMT /").should_succeed "" + 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" + cookbook_artifact "x", "1x1", "metadata.rb" => cb_metadata("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 the cookbook artifact" do + before do + cookbook_artifact "x", "1x1", { "recipes" => { "default.rb" => "" } } + end + + it "should fail because cookbook_artifacts cannot be updated" do + knife("upload /cookbook_artifacts/x-1x1").should_fail <<~EOM + ERROR: /cookbook_artifacts/x-1x1 cannot be updated: cookbook artifacts are immutable once uploaded. + 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" => "" } } + cookbook_artifact "x", "1x1", { "metadata.rb" => cb_metadata("x", "1.0.0") } + 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_group_list" => [ "x" ] } + policy "y", "1.0.0", { "policy_group_list" => [ "x" ] } + policy_group "x", { + "policies" => { + "x" => { "revision_id" => "1.0.0" }, + "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 + 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 "" + expect(api.get("/")["full_name"]).to eq("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" + expect(api.get("/")["full_name"]).to eq("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", %w{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" + expect(api.get("association_requests").map { |a| a["username"] }).to eq(%w{foo foobar}) + expect(api.get("users").map { |a| a["user"]["username"] }).to eq([ "bar" ]) + end + end + + when_the_repository "wants to make foo, bar and foobar members" do + before do + file "members.json", %w{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" + expect(api.get("association_requests").map { |a| a["username"] }).to eq([ ]) + expect(api.get("users").map { |a| a["user"]["username"] }).to eq(%w{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 "" + 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 + 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", %w{foo bar} + end + + it "knife upload / does nothing" do + knife("upload /").should_succeed "" + expect(api.get("association_requests").map { |a| a["username"] }).to eq(%w{bar foo}) + expect(api.get("users").map { |a| a["user"]["username"] }).to eq([ ]) + 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", %w{foo bar} + end + + it "knife upload / does nothing" do + knife("upload /").should_succeed "" + expect(api.get("association_requests").map { |a| a["username"] }).to eq([ ]) + expect(api.get("users").map { |a| a["user"]["username"] }).to eq(%w{bar foo}) + end + end + end + end + end +end