# # Author:: John Keiser () # Copyright:: Copyright 2013-2016, 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 "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 < "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 < 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", <= 13" do knife("upload /cookbooks").should_fail "" # FIXME: include the error message here 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 < /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 < 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 < "aaa", "data_bag" => "bbb" } end it "upload preserves chef_type and data_bag" do knife("upload /data_bags/x/y.json").should_succeed < 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 < {}, "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 < /USAGE/ end it "knife upload --purge . uploads everything" do knife("upload --purge .").should_succeed < "" } 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 < 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 < "" } 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 < "" } cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" } end it "knife upload /cookbooks/x uploads the local version" do knife("upload --purge /cookbooks/x").should_succeed < "hi" } end it "knife upload /cookbooks/x uploads the local version" do knife("diff --name-status /cookbooks").should_succeed < "hi" } end it "knife upload /cookbooks/x uploads the new version" do knife("upload --purge /cookbooks/x").should_succeed < 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 < {} } 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 < "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 < 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", < 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 < /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 < {}, "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 < /USAGE/ end it "knife upload --purge . uploads everything" do knife("upload --purge .").should_succeed < "" } 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 < "" } 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 < "" } 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 < "hi" } end it "knife upload /cookbooks/x uploads the local version" do knife("diff --name-status /cookbooks").should_succeed < "hi" } end it "knife upload /cookbooks/x uploads the new version" do knife("upload --purge /cookbooks").should_succeed < "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 < 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", {} file "policies/blah-1.0.0.json", {} file "policy_groups/x.json", { "policies" => { "x" => { "revision_id" => "1.0.0" }, "blah" => { "revision_id" => "1.0.0" } } } file "roles/x.json", {} end it "knife upload / uploads everything" do knife("upload /").should_succeed < "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 < [ "blah" ] } end it "should fail because policies are not updateable" do knife("upload /policies/x-1.0.0.json").should_fail < { "default.rb" => "" } } end it "should fail because cookbook_artifacts cannot be updated" do knife("upload /cookbook_artifacts/x-1x1").should_fail < 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 "x", "1.0.1", {} policy "y", "1.0.0", {} policy_group "x", { "policies" => { "x" => { "revision_id" => "1.0.1" }, "y" => { "revision_id" => "1.0.0" }, }, } role "x", { "run_list" => [ "blah" ] } end it "knife upload updates everything" do knife("upload /").should_succeed < "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