# # Author:: John Keiser () # Copyright:: Copyright (c) 2013-2015 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', {} 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', < 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([ '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([ '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', {} 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').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 one of each thing' do before do # TODO We have to upload acls for an existing group due to a lack of # dependency detection during upload. Fix that! file 'acls/groups/blah.json', {} file 'clients/x.json', { 'public_key' => ChefZero::PUBLIC_KEY } file 'containers/x.json', {} file 'cookbooks/x/metadata.rb', cb_metadata("x", "1.0.0") file 'data_bags/x/y.json', {} file 'environments/x.json', {} file 'groups/x.json', {} file 'invitations.json', [ 'foo' ] file 'members.json', [ 'bar' ] file 'nodes/x.json', {} file 'org.json', { 'full_name' => 'wootles' } file 'roles/x.json', {} end it 'knife upload / uploads everything' do knife('upload /').should_succeed < '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', [ '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([ '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', [ '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([ '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', [ 'foo', 'bar' ] end it 'knife upload / does nothing' do knife('upload /').should_succeed '' expect(api.get('association_requests').map { |a| a['username'] }).to eq([ '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', [ '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([ 'bar', 'foo' ]) end end end end end end