diff options
author | Marc A. Paradise <marc.paradise@gmail.com> | 2021-04-29 15:21:51 -0400 |
---|---|---|
committer | Marc A. Paradise <marc.paradise@gmail.com> | 2021-04-30 13:21:02 -0400 |
commit | 5e9d7cfa7c093f5c9208eab3f81bedb042b4e90c (patch) | |
tree | 56350ae65748d4d257c736020b3a3c15ff209c7d /knife/spec | |
parent | 15624a8560b6939a5211900a118bdfff65736391 (diff) | |
download | chef-5e9d7cfa7c093f5c9208eab3f81bedb042b4e90c.tar.gz |
Move knife spec files into the knife gem
That's where they belong, now that we've split the gem out.
Signed-off-by: Marc A. Paradise <marc.paradise@gmail.com>
Diffstat (limited to 'knife/spec')
564 files changed, 34676 insertions, 0 deletions
diff --git a/knife/spec/data/apt/chef-integration-test-1.0/debian/changelog b/knife/spec/data/apt/chef-integration-test-1.0/debian/changelog new file mode 100644 index 0000000000..bb34505e65 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test-1.0/debian/changelog @@ -0,0 +1,5 @@ +chef-integration-test (1.0-1) unstable; urgency=low + + * Initial release (Closes: #CHEF-1718) + + -- Joshua Timberman <joshua@opscode.com> Thu, 30 Sep 2010 09:53:45 -0600 diff --git a/knife/spec/data/apt/chef-integration-test-1.0/debian/compat b/knife/spec/data/apt/chef-integration-test-1.0/debian/compat new file mode 100644 index 0000000000..7f8f011eb7 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test-1.0/debian/compat @@ -0,0 +1 @@ +7 diff --git a/knife/spec/data/apt/chef-integration-test-1.0/debian/control b/knife/spec/data/apt/chef-integration-test-1.0/debian/control new file mode 100644 index 0000000000..e77b01b1d2 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test-1.0/debian/control @@ -0,0 +1,13 @@ +Source: chef-integration-test +Section: ruby +Priority: extra +Maintainer: Joshua Timberman <Joshua Timberman <joshua@opscode.com>> +Build-Depends: debhelper (>= 7.0.50~) +Standards-Version: 3.8.4 +Homepage: http://tickets.opscode.com + +Package: chef-integration-test +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: Chef integration tests for APT in Cucumber + This package is used for cucumber integration testing in Chef. diff --git a/knife/spec/data/apt/chef-integration-test-1.0/debian/copyright b/knife/spec/data/apt/chef-integration-test-1.0/debian/copyright new file mode 100644 index 0000000000..e840a11cca --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test-1.0/debian/copyright @@ -0,0 +1,34 @@ +This work was packaged by: + + Joshua Timberman <Joshua Timberman <joshua@opscode.com>> on Thu, 30 Sep 2010 09:53:45 -0600 + +Upstream Author(s): + + Opscode, Inc. + +Copyright: + + Copyright 2010-2016, Chef Software Inc. + +License: + + 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. + +The Debian packaging is: + + Copyright 2010-2016, Chef Software Inc. (<legal@chef.io>) + + +and is licensed under the Apache 2.0 license. + +See "/usr/share/common-licenses/Apache-2.0" diff --git a/knife/spec/data/apt/chef-integration-test-1.0/debian/files b/knife/spec/data/apt/chef-integration-test-1.0/debian/files new file mode 100644 index 0000000000..536f4beabc --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test-1.0/debian/files @@ -0,0 +1 @@ +chef-integration-test_1.0-1_amd64.deb ruby extra diff --git a/knife/spec/data/apt/chef-integration-test-1.0/debian/rules b/knife/spec/data/apt/chef-integration-test-1.0/debian/rules new file mode 100755 index 0000000000..b760bee7f4 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test-1.0/debian/rules @@ -0,0 +1,13 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +%: + dh $@ diff --git a/knife/spec/data/apt/chef-integration-test-1.0/debian/source/format b/knife/spec/data/apt/chef-integration-test-1.0/debian/source/format new file mode 100644 index 0000000000..163aaf8d82 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test-1.0/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/knife/spec/data/apt/chef-integration-test-1.1/debian/changelog b/knife/spec/data/apt/chef-integration-test-1.1/debian/changelog new file mode 100644 index 0000000000..fc693c1ec8 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test-1.1/debian/changelog @@ -0,0 +1,11 @@ +chef-integration-test (1.1-1) unstable; urgency=low + + * New upstream release (1.1) + + -- Joshua Timberman <joshua@opscode.com> Thu, 30 Sep 2010 10:09:34 -0600 + +chef-integration-test (1.0-1) unstable; urgency=low + + * Initial release (Closes: #CHEF-1718) + + -- Joshua Timberman <joshua@opscode.com> Thu, 30 Sep 2010 09:53:45 -0600 diff --git a/knife/spec/data/apt/chef-integration-test-1.1/debian/compat b/knife/spec/data/apt/chef-integration-test-1.1/debian/compat new file mode 100644 index 0000000000..7f8f011eb7 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test-1.1/debian/compat @@ -0,0 +1 @@ +7 diff --git a/knife/spec/data/apt/chef-integration-test-1.1/debian/control b/knife/spec/data/apt/chef-integration-test-1.1/debian/control new file mode 100644 index 0000000000..e77b01b1d2 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test-1.1/debian/control @@ -0,0 +1,13 @@ +Source: chef-integration-test +Section: ruby +Priority: extra +Maintainer: Joshua Timberman <Joshua Timberman <joshua@opscode.com>> +Build-Depends: debhelper (>= 7.0.50~) +Standards-Version: 3.8.4 +Homepage: http://tickets.opscode.com + +Package: chef-integration-test +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: Chef integration tests for APT in Cucumber + This package is used for cucumber integration testing in Chef. diff --git a/knife/spec/data/apt/chef-integration-test-1.1/debian/copyright b/knife/spec/data/apt/chef-integration-test-1.1/debian/copyright new file mode 100644 index 0000000000..e840a11cca --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test-1.1/debian/copyright @@ -0,0 +1,34 @@ +This work was packaged by: + + Joshua Timberman <Joshua Timberman <joshua@opscode.com>> on Thu, 30 Sep 2010 09:53:45 -0600 + +Upstream Author(s): + + Opscode, Inc. + +Copyright: + + Copyright 2010-2016, Chef Software Inc. + +License: + + 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. + +The Debian packaging is: + + Copyright 2010-2016, Chef Software Inc. (<legal@chef.io>) + + +and is licensed under the Apache 2.0 license. + +See "/usr/share/common-licenses/Apache-2.0" diff --git a/knife/spec/data/apt/chef-integration-test-1.1/debian/files b/knife/spec/data/apt/chef-integration-test-1.1/debian/files new file mode 100644 index 0000000000..d72553c027 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test-1.1/debian/files @@ -0,0 +1 @@ +chef-integration-test_1.1-1_amd64.deb ruby extra diff --git a/knife/spec/data/apt/chef-integration-test-1.1/debian/rules b/knife/spec/data/apt/chef-integration-test-1.1/debian/rules new file mode 100755 index 0000000000..b760bee7f4 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test-1.1/debian/rules @@ -0,0 +1,13 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +%: + dh $@ diff --git a/knife/spec/data/apt/chef-integration-test-1.1/debian/source/format b/knife/spec/data/apt/chef-integration-test-1.1/debian/source/format new file mode 100644 index 0000000000..163aaf8d82 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test-1.1/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/changelog b/knife/spec/data/apt/chef-integration-test2-1.0/debian/changelog new file mode 100644 index 0000000000..1b846f8f4d --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/changelog @@ -0,0 +1,5 @@ +chef-integration-test2 (1.0-1) unstable; urgency=low + + * Initial release (Closes: #CHEF-1718) + + -- Joshua Timberman <joshua@opscode.com> Thu, 30 Sep 2010 09:53:45 -0600 diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.debhelper.log b/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.debhelper.log new file mode 100644 index 0000000000..2d06fcdad9 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.debhelper.log @@ -0,0 +1,45 @@ +dh_auto_configure +dh_auto_build +dh_auto_test +dh_prep +dh_installdirs +dh_auto_install +dh_install +dh_installdocs +dh_installchangelogs +dh_installexamples +dh_installman +dh_installcatalogs +dh_installcron +dh_installdebconf +dh_installemacsen +dh_installifupdown +dh_installinfo +dh_pysupport +dh_installinit +dh_installmenu +dh_installmime +dh_installmodules +dh_installlogcheck +dh_installlogrotate +dh_installpam +dh_installppp +dh_installudev +dh_installwm +dh_installxfonts +dh_bugfiles +dh_lintian +dh_gconf +dh_icons +dh_perl +dh_usrlocal +dh_link +dh_compress +dh_fixperms +dh_strip +dh_makeshlibs +dh_shlibdeps +dh_installdeb +dh_gencontrol +dh_md5sums +dh_builddeb diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.substvars b/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.substvars new file mode 100644 index 0000000000..abd3ebebc3 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.substvars @@ -0,0 +1 @@ +misc:Depends= diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/conffiles b/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/conffiles new file mode 100644 index 0000000000..ac4307eadf --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/conffiles @@ -0,0 +1 @@ +/usr/share/doc/chef-integration-test2/copyright diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/control b/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/control new file mode 100644 index 0000000000..27d53d9750 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/control @@ -0,0 +1,10 @@ +Package: chef-integration-test2 +Version: 1.0-1 +Architecture: amd64 +Maintainer: Joshua Timberman <Joshua Timberman <joshua@opscode.com>> +Installed-Size: 36 +Section: ruby +Priority: extra +Homepage: http://tickets.opscode.com +Description: Chef integration tests for APT in Cucumber + This package is used for cucumber integration testing in Chef. diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/md5sums b/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/md5sums new file mode 100644 index 0000000000..144b7931de --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/md5sums @@ -0,0 +1 @@ +8b3b9ff6cdfe7d7b2b8b8d4f7b9e381f usr/share/doc/chef-integration-test2/changelog.Debian.gz diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/compat b/knife/spec/data/apt/chef-integration-test2-1.0/debian/compat new file mode 100644 index 0000000000..7f8f011eb7 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/compat @@ -0,0 +1 @@ +7 diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/conffiles b/knife/spec/data/apt/chef-integration-test2-1.0/debian/conffiles new file mode 100644 index 0000000000..ac4307eadf --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/conffiles @@ -0,0 +1 @@ +/usr/share/doc/chef-integration-test2/copyright diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/control b/knife/spec/data/apt/chef-integration-test2-1.0/debian/control new file mode 100644 index 0000000000..f2731a6848 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/control @@ -0,0 +1,13 @@ +Source: chef-integration-test2 +Section: ruby +Priority: extra +Maintainer: Joshua Timberman <Joshua Timberman <joshua@opscode.com>> +Build-Depends: debhelper (>= 7.0.50~) +Standards-Version: 3.8.4 +Homepage: http://tickets.opscode.com + +Package: chef-integration-test2 +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: Chef integration tests for APT in Cucumber + This package is used for cucumber integration testing in Chef. diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/copyright b/knife/spec/data/apt/chef-integration-test2-1.0/debian/copyright new file mode 100644 index 0000000000..e840a11cca --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/copyright @@ -0,0 +1,34 @@ +This work was packaged by: + + Joshua Timberman <Joshua Timberman <joshua@opscode.com>> on Thu, 30 Sep 2010 09:53:45 -0600 + +Upstream Author(s): + + Opscode, Inc. + +Copyright: + + Copyright 2010-2016, Chef Software Inc. + +License: + + 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. + +The Debian packaging is: + + Copyright 2010-2016, Chef Software Inc. (<legal@chef.io>) + + +and is licensed under the Apache 2.0 license. + +See "/usr/share/common-licenses/Apache-2.0" diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/files b/knife/spec/data/apt/chef-integration-test2-1.0/debian/files new file mode 100644 index 0000000000..640e4c6414 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/files @@ -0,0 +1 @@ +chef-integration-test2_1.0-1_amd64.deb ruby extra diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/rules b/knife/spec/data/apt/chef-integration-test2-1.0/debian/rules new file mode 100755 index 0000000000..b760bee7f4 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/rules @@ -0,0 +1,13 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +%: + dh $@ diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/source/format b/knife/spec/data/apt/chef-integration-test2-1.0/debian/source/format new file mode 100644 index 0000000000..163aaf8d82 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/knife/spec/data/apt/chef-integration-test2_1.0-1.debian.tar.gz b/knife/spec/data/apt/chef-integration-test2_1.0-1.debian.tar.gz Binary files differnew file mode 100644 index 0000000000..6c002a7420 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test2_1.0-1.debian.tar.gz diff --git a/knife/spec/data/apt/chef-integration-test2_1.0-1.dsc b/knife/spec/data/apt/chef-integration-test2_1.0-1.dsc new file mode 100644 index 0000000000..b247f49346 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test2_1.0-1.dsc @@ -0,0 +1,18 @@ +Format: 3.0 (quilt) +Source: chef-integration-test2 +Binary: chef-integration-test2 +Architecture: any +Version: 1.0-1 +Maintainer: Joshua Timberman <Joshua Timberman <joshua@opscode.com>> +Homepage: http://tickets.opscode.com +Standards-Version: 3.8.4 +Build-Depends: debhelper (>= 7.0.50~) +Checksums-Sha1: + 755c304197c6559128aed206ea70643fec2bb90d 248 chef-integration-test2_1.0.orig.tar.gz + 8b7df49a9e2c57b4460c2738852db1156a21a089 1369 chef-integration-test2_1.0-1.debian.tar.gz +Checksums-Sha256: + 8b206a7b3d422290bc8d82bd700cb89f1c6e3962b96be6a3955c7a0159f9031c 248 chef-integration-test2_1.0.orig.tar.gz + 77a7956e222c35afcddc4a5a8d338ca6e36dc1fbd720af255ce2412885f82702 1369 chef-integration-test2_1.0-1.debian.tar.gz +Files: + f1f7d7bbe63ad631d25d707f564a8d33 248 chef-integration-test2_1.0.orig.tar.gz + 4fab5c1cd9a7b47c4f319af776f48a1d 1369 chef-integration-test2_1.0-1.debian.tar.gz diff --git a/knife/spec/data/apt/chef-integration-test2_1.0-1_amd64.build b/knife/spec/data/apt/chef-integration-test2_1.0-1_amd64.build new file mode 100644 index 0000000000..8ef31d3ccf --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test2_1.0-1_amd64.build @@ -0,0 +1,91 @@ + dpkg-buildpackage -rfakeroot -D -us -uc +dpkg-buildpackage: warning: using a gain-root-command while being root +dpkg-buildpackage: set CFLAGS to default value: -g -O2 +dpkg-buildpackage: set CPPFLAGS to default value: +dpkg-buildpackage: set LDFLAGS to default value: -Wl,-Bsymbolic-functions +dpkg-buildpackage: set FFLAGS to default value: -g -O2 +dpkg-buildpackage: set CXXFLAGS to default value: -g -O2 +dpkg-buildpackage: source package chef-integration-test2 +dpkg-buildpackage: source version 1.0-1 +dpkg-buildpackage: source changed by Joshua Timberman <joshua@opscode.com> +dpkg-buildpackage: host architecture amd64 + fakeroot debian/rules clean +dh clean + dh_testdir + dh_auto_clean + dh_clean + dpkg-source -b chef-integration-test2-1.0 +dpkg-source: info: using source format `3.0 (quilt)' +dpkg-source: info: building chef-integration-test2 using existing ./chef-integration-test2_1.0.orig.tar.gz +dpkg-source: warning: ignoring deletion of directory cache +dpkg-source: warning: ignoring deletion of directory cache/chef-integration-test2 +dpkg-source: warning: ignoring deletion of file cache/chef-integration-test2/contents +dpkg-source: info: building chef-integration-test2 in chef-integration-test2_1.0-1.debian.tar.gz +dpkg-source: info: building chef-integration-test2 in chef-integration-test2_1.0-1.dsc + debian/rules build +dh build + dh_testdir + dh_auto_configure + dh_auto_build + dh_auto_test + fakeroot debian/rules binary +dh binary + dh_testroot + dh_prep + dh_installdirs + dh_auto_install + dh_install + dh_installdocs + dh_installchangelogs + dh_installexamples + dh_installman + dh_installcatalogs + dh_installcron + dh_installdebconf + dh_installemacsen + dh_installifupdown + dh_installinfo + dh_pysupport + dh_installinit + dh_installmenu + dh_installmime + dh_installmodules + dh_installlogcheck + dh_installlogrotate + dh_installpam + dh_installppp + dh_installudev + dh_installwm + dh_installxfonts + dh_bugfiles + dh_lintian + dh_gconf + dh_icons + dh_perl + dh_usrlocal + dh_link + dh_compress + dh_fixperms + dh_strip + dh_makeshlibs + dh_shlibdeps + dh_installdeb + dh_gencontrol +dpkg-gencontrol: warning: unknown substitution variable ${shlibs:Depends} + dh_md5sums + dh_builddeb +dpkg-deb: building package `chef-integration-test2' in `../chef-integration-test2_1.0-1_amd64.deb'. + dpkg-genchanges >../chef-integration-test2_1.0-1_amd64.changes +dpkg-genchanges: including full source code in upload +dpkg-buildpackage: full upload (original source is included) +Now running lintian... +warning: lintian's authors do not recommend running it with root privileges! +E: chef-integration-test2 source: maintainer-address-malformed Joshua Timberman <Joshua Timberman <joshua@opscode.com>> +W: chef-integration-test2 source: changelog-should-mention-nmu +W: chef-integration-test2 source: source-nmu-has-incorrect-version-number 1.0-1 +W: chef-integration-test2: new-package-should-close-itp-bug +W: chef-integration-test2: wrong-bug-number-in-closes l3:#CHEF +E: chef-integration-test2: file-in-usr-marked-as-conffile /usr/share/doc/chef-integration-test2/copyright +E: chef-integration-test2: maintainer-address-malformed Joshua Timberman <Joshua Timberman <joshua@opscode.com>> +W: chef-integration-test2: empty-binary-package +Finished running lintian. diff --git a/knife/spec/data/apt/chef-integration-test2_1.0-1_amd64.changes b/knife/spec/data/apt/chef-integration-test2_1.0-1_amd64.changes new file mode 100644 index 0000000000..be3cd45343 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test2_1.0-1_amd64.changes @@ -0,0 +1,31 @@ +Format: 1.8 +Date: Thu, 30 Sep 2010 09:53:45 -0600 +Source: chef-integration-test2 +Binary: chef-integration-test2 +Architecture: source amd64 +Version: 1.0-1 +Distribution: unstable +Urgency: low +Maintainer: Joshua Timberman <Joshua Timberman <joshua@opscode.com>> +Changed-By: Joshua Timberman <joshua@opscode.com> +Description: + chef-integration-test2 - Chef integration tests for APT in Cucumber +Changes: + chef-integration-test2 (1.0-1) unstable; urgency=low + . + * Initial release (Closes: #CHEF-1718) +Checksums-Sha1: + 7e065fdf71f4d798312b318a29cec43b7bc1c8e1 885 chef-integration-test2_1.0-1.dsc + 755c304197c6559128aed206ea70643fec2bb90d 248 chef-integration-test2_1.0.orig.tar.gz + 8b7df49a9e2c57b4460c2738852db1156a21a089 1369 chef-integration-test2_1.0-1.debian.tar.gz + f3f89c051bce36d40ef1be12d231c44b2d5be05b 1694 chef-integration-test2_1.0-1_amd64.deb +Checksums-Sha256: + 80d314349e1d978f242d05482ca81c9361739047daa4adcecc9e5e622fdc6fb4 885 chef-integration-test2_1.0-1.dsc + 8b206a7b3d422290bc8d82bd700cb89f1c6e3962b96be6a3955c7a0159f9031c 248 chef-integration-test2_1.0.orig.tar.gz + 77a7956e222c35afcddc4a5a8d338ca6e36dc1fbd720af255ce2412885f82702 1369 chef-integration-test2_1.0-1.debian.tar.gz + 19a767db0a947a350fb1c8492699e8a808fbe1838d4a582001106cfbe520ad8f 1694 chef-integration-test2_1.0-1_amd64.deb +Files: + 9f927b32d95b5406c696b5b0b23177e8 885 ruby extra chef-integration-test2_1.0-1.dsc + f1f7d7bbe63ad631d25d707f564a8d33 248 ruby extra chef-integration-test2_1.0.orig.tar.gz + 4fab5c1cd9a7b47c4f319af776f48a1d 1369 ruby extra chef-integration-test2_1.0-1.debian.tar.gz + 9914e6152e278b6828bcade3b3f5580c 1694 ruby extra chef-integration-test2_1.0-1_amd64.deb diff --git a/knife/spec/data/apt/chef-integration-test2_1.0-1_amd64.deb b/knife/spec/data/apt/chef-integration-test2_1.0-1_amd64.deb Binary files differnew file mode 100644 index 0000000000..7b9b69d378 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test2_1.0-1_amd64.deb diff --git a/knife/spec/data/apt/chef-integration-test2_1.0.orig.tar.gz b/knife/spec/data/apt/chef-integration-test2_1.0.orig.tar.gz Binary files differnew file mode 100644 index 0000000000..18f7aa17d6 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test2_1.0.orig.tar.gz diff --git a/knife/spec/data/apt/chef-integration-test_1.0-1_amd64.changes b/knife/spec/data/apt/chef-integration-test_1.0-1_amd64.changes new file mode 100644 index 0000000000..4746b834e5 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test_1.0-1_amd64.changes @@ -0,0 +1,22 @@ +Format: 1.8 +Date: Thu, 30 Sep 2010 09:53:45 -0600 +Source: chef-integration-test +Binary: chef-integration-test +Architecture: amd64 +Version: 1.0-1 +Distribution: unstable +Urgency: low +Maintainer: Joshua Timberman <Joshua Timberman <joshua@opscode.com>> +Changed-By: Joshua Timberman <joshua@opscode.com> +Description: + chef-integration-test - Chef integration tests for APT in Cucumber +Changes: + chef-integration-test (1.0-1) unstable; urgency=low + . + * Initial release (Closes: #CHEF-1718) +Checksums-Sha1: + b44685ff59626bc94c67e60665f06c4643fe9767 1680 chef-integration-test_1.0-1_amd64.deb +Checksums-Sha256: + da176f4405fa21fd7207d4785680c6996d395a1ca132f2d5565a61c5479b1116 1680 chef-integration-test_1.0-1_amd64.deb +Files: + 713722480408ecc8e7220aea52bdd76e 1680 ruby extra chef-integration-test_1.0-1_amd64.deb diff --git a/knife/spec/data/apt/chef-integration-test_1.0-1_amd64.deb b/knife/spec/data/apt/chef-integration-test_1.0-1_amd64.deb Binary files differnew file mode 100644 index 0000000000..458dd026ff --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test_1.0-1_amd64.deb diff --git a/knife/spec/data/apt/chef-integration-test_1.0.orig.tar.gz b/knife/spec/data/apt/chef-integration-test_1.0.orig.tar.gz Binary files differnew file mode 100644 index 0000000000..3de028d486 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test_1.0.orig.tar.gz diff --git a/knife/spec/data/apt/chef-integration-test_1.1-1_amd64.changes b/knife/spec/data/apt/chef-integration-test_1.1-1_amd64.changes new file mode 100644 index 0000000000..f014de813b --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test_1.1-1_amd64.changes @@ -0,0 +1,22 @@ +Format: 1.8 +Date: Thu, 30 Sep 2010 10:09:34 -0600 +Source: chef-integration-test +Binary: chef-integration-test +Architecture: amd64 +Version: 1.1-1 +Distribution: unstable +Urgency: low +Maintainer: Joshua Timberman <Joshua Timberman <joshua@opscode.com>> +Changed-By: Joshua Timberman <joshua@opscode.com> +Description: + chef-integration-test - Chef integration tests for APT in Cucumber +Changes: + chef-integration-test (1.1-1) unstable; urgency=low + . + * New upstream release (1.1) +Checksums-Sha1: + 43c5653a9a5b9419849173a4ec3a9855cf0327a3 1722 chef-integration-test_1.1-1_amd64.deb +Checksums-Sha256: + 84e2f087f7e11d1b73743007ecfc6b8b34e03f6917c0993b35c0758ee59702c1 1722 chef-integration-test_1.1-1_amd64.deb +Files: + 4b05bace483dbca54efc21f97ee47e1d 1722 ruby extra chef-integration-test_1.1-1_amd64.deb diff --git a/knife/spec/data/apt/chef-integration-test_1.1-1_amd64.deb b/knife/spec/data/apt/chef-integration-test_1.1-1_amd64.deb Binary files differnew file mode 100644 index 0000000000..c4fac10dc1 --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test_1.1-1_amd64.deb diff --git a/knife/spec/data/apt/chef-integration-test_1.1.orig.tar.gz b/knife/spec/data/apt/chef-integration-test_1.1.orig.tar.gz Binary files differnew file mode 100644 index 0000000000..5fda119eae --- /dev/null +++ b/knife/spec/data/apt/chef-integration-test_1.1.orig.tar.gz diff --git a/knife/spec/data/apt/var/www/apt/conf/distributions b/knife/spec/data/apt/var/www/apt/conf/distributions new file mode 100644 index 0000000000..285c1a88de --- /dev/null +++ b/knife/spec/data/apt/var/www/apt/conf/distributions @@ -0,0 +1,7 @@ +Origin: localhost +Label: apt repository +Codename: sid +Architectures: amd64 +Components: main +Description: Apt repository +Pull: sid diff --git a/knife/spec/data/apt/var/www/apt/conf/incoming b/knife/spec/data/apt/var/www/apt/conf/incoming new file mode 100644 index 0000000000..d44e59c51b --- /dev/null +++ b/knife/spec/data/apt/var/www/apt/conf/incoming @@ -0,0 +1,4 @@ +Name: default +IncomingDir: /tmp/incoming +TempDir: /tmp +Allow: sid unstable>sid diff --git a/knife/spec/data/apt/var/www/apt/conf/pulls b/knife/spec/data/apt/var/www/apt/conf/pulls new file mode 100644 index 0000000000..0fc3358279 --- /dev/null +++ b/knife/spec/data/apt/var/www/apt/conf/pulls @@ -0,0 +1,3 @@ +Name: sid +From: sid +Components: main diff --git a/knife/spec/data/apt/var/www/apt/db/checksums.db b/knife/spec/data/apt/var/www/apt/db/checksums.db Binary files differnew file mode 100644 index 0000000000..e36ade2079 --- /dev/null +++ b/knife/spec/data/apt/var/www/apt/db/checksums.db diff --git a/knife/spec/data/apt/var/www/apt/db/contents.cache.db b/knife/spec/data/apt/var/www/apt/db/contents.cache.db Binary files differnew file mode 100644 index 0000000000..04a0c4aed5 --- /dev/null +++ b/knife/spec/data/apt/var/www/apt/db/contents.cache.db diff --git a/knife/spec/data/apt/var/www/apt/db/packages.db b/knife/spec/data/apt/var/www/apt/db/packages.db Binary files differnew file mode 100644 index 0000000000..43c70b0de3 --- /dev/null +++ b/knife/spec/data/apt/var/www/apt/db/packages.db diff --git a/knife/spec/data/apt/var/www/apt/db/references.db b/knife/spec/data/apt/var/www/apt/db/references.db Binary files differnew file mode 100644 index 0000000000..47c99fe152 --- /dev/null +++ b/knife/spec/data/apt/var/www/apt/db/references.db diff --git a/knife/spec/data/apt/var/www/apt/db/release.caches.db b/knife/spec/data/apt/var/www/apt/db/release.caches.db Binary files differnew file mode 100644 index 0000000000..0e251c5496 --- /dev/null +++ b/knife/spec/data/apt/var/www/apt/db/release.caches.db diff --git a/knife/spec/data/apt/var/www/apt/db/version b/knife/spec/data/apt/var/www/apt/db/version new file mode 100644 index 0000000000..a6908690d9 --- /dev/null +++ b/knife/spec/data/apt/var/www/apt/db/version @@ -0,0 +1,4 @@ +4.2.0 +3.3.0 +bdb4.8.30 +bdb4.8.0 diff --git a/knife/spec/data/apt/var/www/apt/dists/sid/Release b/knife/spec/data/apt/var/www/apt/dists/sid/Release new file mode 100644 index 0000000000..44ccd079bf --- /dev/null +++ b/knife/spec/data/apt/var/www/apt/dists/sid/Release @@ -0,0 +1,19 @@ +Origin: localhost +Label: apt repository +Codename: sid +Date: Thu, 30 Sep 2010 16:33:01 UTC +Architectures: amd64 +Components: main +Description: Apt repository +MD5Sum: + 92ed2cc14e37e9ab23466b27857d29ac 596 main/binary-amd64/Packages + c7726773341137b71cc971d44ddec4f5 394 main/binary-amd64/Packages.gz + 46cd71c965ce0813c94ef78c836cc7d3 104 main/binary-amd64/Release +SHA1: + cde25071c5fcee59cee8dcd773ca419dcb40d946 596 main/binary-amd64/Packages + ce04daff75d4b27371d691d645282b198045544a 394 main/binary-amd64/Packages.gz + 91ca9531e3afa7a540cabdc6030c6f75d315fec7 104 main/binary-amd64/Release +SHA256: + af601ce143f33405425746462973adc0fda3aceb381d1c739851b95ee0814ca3 596 main/binary-amd64/Packages + 15e98119705a08018d4583caabc91d36ba12e6f1c8af0f799a3ec8ca5bfaa80d 394 main/binary-amd64/Packages.gz + 098c599ac5b0a98785336afb2bc9c47002570ffa07dd62321c6f70b9fdb74325 104 main/binary-amd64/Release diff --git a/knife/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Packages b/knife/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Packages new file mode 100644 index 0000000000..209c23cd42 --- /dev/null +++ b/knife/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Packages @@ -0,0 +1,16 @@ +Package: chef-integration-test +Version: 1.1-1 +Architecture: amd64 +Maintainer: Joshua Timberman <Joshua Timberman <joshua@opscode.com>> +Installed-Size: 32 +Homepage: http://tickets.opscode.com +Priority: extra +Section: ruby +Filename: pool/main/c/chef-integration-test/chef-integration-test_1.1-1_amd64.deb +Size: 1722 +SHA256: 84e2f087f7e11d1b73743007ecfc6b8b34e03f6917c0993b35c0758ee59702c1 +SHA1: 43c5653a9a5b9419849173a4ec3a9855cf0327a3 +MD5sum: 4b05bace483dbca54efc21f97ee47e1d +Description: Chef integration tests for APT in Cucumber + This package is used for cucumber integration testing in Chef. + diff --git a/knife/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Packages.gz b/knife/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Packages.gz Binary files differnew file mode 100644 index 0000000000..8a2c1e8980 --- /dev/null +++ b/knife/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Packages.gz diff --git a/knife/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Release b/knife/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Release new file mode 100644 index 0000000000..e913d702a1 --- /dev/null +++ b/knife/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Release @@ -0,0 +1,5 @@ +Component: main +Origin: localhost +Label: apt repository +Architecture: amd64 +Description: Apt repository diff --git a/knife/spec/data/apt/var/www/apt/dists/sid/main/binary-i386/Packages b/knife/spec/data/apt/var/www/apt/dists/sid/main/binary-i386/Packages new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/knife/spec/data/apt/var/www/apt/dists/sid/main/binary-i386/Packages diff --git a/knife/spec/data/apt/var/www/apt/pool/main/c/chef-integration-test/chef-integration-test_1.0-1_amd64.deb b/knife/spec/data/apt/var/www/apt/pool/main/c/chef-integration-test/chef-integration-test_1.0-1_amd64.deb Binary files differnew file mode 100644 index 0000000000..458dd026ff --- /dev/null +++ b/knife/spec/data/apt/var/www/apt/pool/main/c/chef-integration-test/chef-integration-test_1.0-1_amd64.deb diff --git a/knife/spec/data/apt/var/www/apt/pool/main/c/chef-integration-test/chef-integration-test_1.1-1_amd64.deb b/knife/spec/data/apt/var/www/apt/pool/main/c/chef-integration-test/chef-integration-test_1.1-1_amd64.deb Binary files differnew file mode 100644 index 0000000000..c4fac10dc1 --- /dev/null +++ b/knife/spec/data/apt/var/www/apt/pool/main/c/chef-integration-test/chef-integration-test_1.1-1_amd64.deb diff --git a/knife/spec/data/bad-config.rb b/knife/spec/data/bad-config.rb new file mode 100644 index 0000000000..5477a69366 --- /dev/null +++ b/knife/spec/data/bad-config.rb @@ -0,0 +1 @@ +monkey_soup("tastes nice")
\ No newline at end of file diff --git a/knife/spec/data/bootstrap/encrypted_data_bag_secret b/knife/spec/data/bootstrap/encrypted_data_bag_secret new file mode 100644 index 0000000000..ac88558a1a --- /dev/null +++ b/knife/spec/data/bootstrap/encrypted_data_bag_secret @@ -0,0 +1 @@ +supersekret_from_file diff --git a/knife/spec/data/bootstrap/no_proxy.erb b/knife/spec/data/bootstrap/no_proxy.erb new file mode 100644 index 0000000000..6945704386 --- /dev/null +++ b/knife/spec/data/bootstrap/no_proxy.erb @@ -0,0 +1,2 @@ +bash -c ' +<%= config_content %>' diff --git a/knife/spec/data/bootstrap/secret.erb b/knife/spec/data/bootstrap/secret.erb new file mode 100644 index 0000000000..e0ad41576d --- /dev/null +++ b/knife/spec/data/bootstrap/secret.erb @@ -0,0 +1,9 @@ +bash -c ' +<% if encrypted_data_bag_secret -%> +awk NF > /etc/chef/encrypted_data_bag_secret <<'EOP' +<%= encrypted_data_bag_secret %> +EOP +chmod 0600 /etc/chef/encrypted_data_bag_secret +<% end -%> + +<%= config_content %>' diff --git a/knife/spec/data/bootstrap/test-hints.erb b/knife/spec/data/bootstrap/test-hints.erb new file mode 100644 index 0000000000..7693fdc7c9 --- /dev/null +++ b/knife/spec/data/bootstrap/test-hints.erb @@ -0,0 +1,12 @@ +<%# Generate Ohai Hints -%> +<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%> +mkdir -p /etc/chef/ohai/hints +<% end -%> + +<% @chef_config[:knife][:hints].each do |name, hash| -%> +( +cat <<'EOP' +<%= Chef::JSONCompat.to_json(hash) %> +EOP +) > /etc/chef/ohai/hints/<%= name %>.json +<% end -%> diff --git a/knife/spec/data/bootstrap/test.erb b/knife/spec/data/bootstrap/test.erb new file mode 100644 index 0000000000..3a383b47d0 --- /dev/null +++ b/knife/spec/data/bootstrap/test.erb @@ -0,0 +1 @@ +<%= Chef::JSONCompat.to_json(first_boot) %> diff --git a/knife/spec/data/cb_version_cookbooks/cookbook2/files/test.txt b/knife/spec/data/cb_version_cookbooks/cookbook2/files/test.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/knife/spec/data/cb_version_cookbooks/cookbook2/files/test.txt diff --git a/knife/spec/data/cb_version_cookbooks/cookbook2/templates/test.erb b/knife/spec/data/cb_version_cookbooks/cookbook2/templates/test.erb new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/knife/spec/data/cb_version_cookbooks/cookbook2/templates/test.erb diff --git a/knife/spec/data/cb_version_cookbooks/tatft/README.rdoc b/knife/spec/data/cb_version_cookbooks/tatft/README.rdoc new file mode 100644 index 0000000000..460d96b40d --- /dev/null +++ b/knife/spec/data/cb_version_cookbooks/tatft/README.rdoc @@ -0,0 +1,3 @@ += THIS RECIPE +* is for testing CookbookLoader/CookbookVersion +* has at least one of every kind of file that cookbooks can have
\ No newline at end of file diff --git a/knife/spec/data/cb_version_cookbooks/tatft/attributes/default.rb b/knife/spec/data/cb_version_cookbooks/tatft/attributes/default.rb new file mode 100644 index 0000000000..47774459c2 --- /dev/null +++ b/knife/spec/data/cb_version_cookbooks/tatft/attributes/default.rb @@ -0,0 +1 @@ +#one_of_each default attributes diff --git a/knife/spec/data/cb_version_cookbooks/tatft/definitions/runit_service.rb b/knife/spec/data/cb_version_cookbooks/tatft/definitions/runit_service.rb new file mode 100644 index 0000000000..3912b37365 --- /dev/null +++ b/knife/spec/data/cb_version_cookbooks/tatft/definitions/runit_service.rb @@ -0,0 +1 @@ +# IRL the runit_service is a definition to set up runit services
\ No newline at end of file diff --git a/knife/spec/data/cb_version_cookbooks/tatft/files/default/giant_blob.tgz b/knife/spec/data/cb_version_cookbooks/tatft/files/default/giant_blob.tgz new file mode 100644 index 0000000000..3e7496601e --- /dev/null +++ b/knife/spec/data/cb_version_cookbooks/tatft/files/default/giant_blob.tgz @@ -0,0 +1 @@ +# not really a giant blob #
\ No newline at end of file diff --git a/knife/spec/data/cb_version_cookbooks/tatft/libraries/ownage.rb b/knife/spec/data/cb_version_cookbooks/tatft/libraries/ownage.rb new file mode 100644 index 0000000000..fea05ba67b --- /dev/null +++ b/knife/spec/data/cb_version_cookbooks/tatft/libraries/ownage.rb @@ -0,0 +1 @@ +# 0wnage
\ No newline at end of file diff --git a/knife/spec/data/cb_version_cookbooks/tatft/providers/lwp.rb b/knife/spec/data/cb_version_cookbooks/tatft/providers/lwp.rb new file mode 100644 index 0000000000..977ad19192 --- /dev/null +++ b/knife/spec/data/cb_version_cookbooks/tatft/providers/lwp.rb @@ -0,0 +1 @@ +# a LWP
\ No newline at end of file diff --git a/knife/spec/data/cb_version_cookbooks/tatft/recipes/default.rb b/knife/spec/data/cb_version_cookbooks/tatft/recipes/default.rb new file mode 100644 index 0000000000..48eacf848b --- /dev/null +++ b/knife/spec/data/cb_version_cookbooks/tatft/recipes/default.rb @@ -0,0 +1 @@ +# the default recipe
\ No newline at end of file diff --git a/knife/spec/data/cb_version_cookbooks/tatft/resources/lwr.rb b/knife/spec/data/cb_version_cookbooks/tatft/resources/lwr.rb new file mode 100644 index 0000000000..987114f4ca --- /dev/null +++ b/knife/spec/data/cb_version_cookbooks/tatft/resources/lwr.rb @@ -0,0 +1 @@ +# a LWR #
\ No newline at end of file diff --git a/knife/spec/data/cb_version_cookbooks/tatft/templates/default/configuration.erb b/knife/spec/data/cb_version_cookbooks/tatft/templates/default/configuration.erb new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/knife/spec/data/cb_version_cookbooks/tatft/templates/default/configuration.erb diff --git a/knife/spec/data/checksum/random.txt b/knife/spec/data/checksum/random.txt new file mode 100644 index 0000000000..9570fff833 --- /dev/null +++ b/knife/spec/data/checksum/random.txt @@ -0,0 +1 @@ +e4010abcac515ef3b78e92ee1f848a0d3bc3a526fcb826e7b4d39a6d516aa0487085c9b1be35e8d909617b250dca36dd4a55f01b7cdd310826bfd748cb27e0e43dd52b22968383c8086b06ee2d16e13574f98c058ce2bc3475b92ecf9c16e504022d60b132643986a8e7908d067526e20b4bafe1eb75349f27a4d3de02b077e76a2f59b73c14413f11e7208ae0bf6a408d51a97d490530e23476960ab8780ad86349947d82f1c9e57c85f86d71f80a6709b58be5f993a6a6df80c5a0857627d4a01e71484f6a6e983985089c00fe538e947230813c3a3e19baf6dae6db7082d07392a239ec1be385646356db3e3d76571488a6c72f0b96997f6191beea9846fc99f82a828f05af95cfc234cf681002f830915b1f3d35b2178b54a861c05d2694c5f6cfeb613a4a3670d849180461cdedf2c3cbb022608d8b19c86179d2d6da6b9acefccfc34b59663ef1282fec262bef79b2fbdd9b6669c90d6817b3762164dc309616469b33b83b1ded3420ae9177bc8f456d83939ff3c91b0a3683f3157401ceadf679c9f876da2aa413e081ee4c41d4b04f49e0c254d0082fd9bf2cb8eb8b966285be2cdcaab0ab70ea970737244b6683283598c30bdc206a05df72048b342eb40c2cd750c815d5fa944167b103ec40d60a99c49941a9e76d874149524f35ca294d081cf221757df77e027640556d983978be6b4b51aff26cd74a2f300d71
\ No newline at end of file diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-600hhz-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-600hhz-0 new file mode 100644 index 0000000000..81836588d5 --- /dev/null +++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-600hhz-0 @@ -0,0 +1 @@ +checksum data here diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-6m8zdk-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-6m8zdk-0 new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-6m8zdk-0 diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ahd2gq-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ahd2gq-0 new file mode 100644 index 0000000000..81836588d5 --- /dev/null +++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ahd2gq-0 @@ -0,0 +1 @@ +checksum data here diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-api8ux-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-api8ux-0 new file mode 100644 index 0000000000..81836588d5 --- /dev/null +++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-api8ux-0 @@ -0,0 +1 @@ +checksum data here diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-b0r1m1-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-b0r1m1-0 new file mode 100644 index 0000000000..81836588d5 --- /dev/null +++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-b0r1m1-0 @@ -0,0 +1 @@ +checksum data here diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-bfygsi-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-bfygsi-0 new file mode 100644 index 0000000000..81836588d5 --- /dev/null +++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-bfygsi-0 @@ -0,0 +1 @@ +checksum data here diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-el14l6-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-el14l6-0 new file mode 100644 index 0000000000..81836588d5 --- /dev/null +++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-el14l6-0 @@ -0,0 +1 @@ +checksum data here diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ivrl3y-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ivrl3y-0 new file mode 100644 index 0000000000..81836588d5 --- /dev/null +++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ivrl3y-0 @@ -0,0 +1 @@ +checksum data here diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-kkbs85-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-kkbs85-0 new file mode 100644 index 0000000000..81836588d5 --- /dev/null +++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-kkbs85-0 @@ -0,0 +1 @@ +checksum data here diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ory1ux-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ory1ux-0 new file mode 100644 index 0000000000..81836588d5 --- /dev/null +++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ory1ux-0 @@ -0,0 +1 @@ +checksum data here diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-pgsq76-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-pgsq76-0 new file mode 100644 index 0000000000..81836588d5 --- /dev/null +++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-pgsq76-0 @@ -0,0 +1 @@ +checksum data here diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ra8uim-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ra8uim-0 new file mode 100644 index 0000000000..81836588d5 --- /dev/null +++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ra8uim-0 @@ -0,0 +1 @@ +checksum data here diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-t7k1g-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-t7k1g-0 new file mode 100644 index 0000000000..81836588d5 --- /dev/null +++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-t7k1g-0 @@ -0,0 +1 @@ +checksum data here diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-t8g0sv-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-t8g0sv-0 new file mode 100644 index 0000000000..81836588d5 --- /dev/null +++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-t8g0sv-0 @@ -0,0 +1 @@ +checksum data here diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ufy6g3-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ufy6g3-0 new file mode 100644 index 0000000000..81836588d5 --- /dev/null +++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ufy6g3-0 @@ -0,0 +1 @@ +checksum data here diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-x2d6j9-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-x2d6j9-0 new file mode 100644 index 0000000000..81836588d5 --- /dev/null +++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-x2d6j9-0 @@ -0,0 +1 @@ +checksum data here diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-xi0l6h-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-xi0l6h-0 new file mode 100644 index 0000000000..81836588d5 --- /dev/null +++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-xi0l6h-0 @@ -0,0 +1 @@ +checksum data here diff --git a/knife/spec/data/client.d_00/00-foo.rb b/knife/spec/data/client.d_00/00-foo.rb new file mode 100644 index 0000000000..44a763aca1 --- /dev/null +++ b/knife/spec/data/client.d_00/00-foo.rb @@ -0,0 +1,2 @@ +# 00-foo.rb +# d6f9b976-289c-4149-baf7-81e6ffecf228 diff --git a/knife/spec/data/client.d_00/01-bar.rb b/knife/spec/data/client.d_00/01-bar.rb new file mode 100644 index 0000000000..73f91386bc --- /dev/null +++ b/knife/spec/data/client.d_00/01-bar.rb @@ -0,0 +1 @@ +# 01-bar.rb diff --git a/knife/spec/data/client.d_00/02-strings.rb b/knife/spec/data/client.d_00/02-strings.rb new file mode 100644 index 0000000000..7d9a49268c --- /dev/null +++ b/knife/spec/data/client.d_00/02-strings.rb @@ -0,0 +1,2 @@ +# 02-strings.rb +something '/foo/bar' diff --git a/knife/spec/data/client.d_00/bar b/knife/spec/data/client.d_00/bar new file mode 100644 index 0000000000..72dca4d5e4 --- /dev/null +++ b/knife/spec/data/client.d_00/bar @@ -0,0 +1 @@ +1 / 0 diff --git a/knife/spec/data/client.d_01/foo/bar.rb b/knife/spec/data/client.d_01/foo/bar.rb new file mode 100644 index 0000000000..72dca4d5e4 --- /dev/null +++ b/knife/spec/data/client.d_01/foo/bar.rb @@ -0,0 +1 @@ +1 / 0 diff --git a/knife/spec/data/client.d_02/foo.rb/foo.txt b/knife/spec/data/client.d_02/foo.rb/foo.txt new file mode 100644 index 0000000000..d724c93bef --- /dev/null +++ b/knife/spec/data/client.d_02/foo.rb/foo.txt @@ -0,0 +1 @@ +# foo.txt diff --git a/knife/spec/data/config.rb b/knife/spec/data/config.rb new file mode 100644 index 0000000000..0b3340ce57 --- /dev/null +++ b/knife/spec/data/config.rb @@ -0,0 +1,6 @@ +# +# Sample Chef Config File +# + +cookbook_path "/etc/chef/cookbook", "/etc/chef/site-cookbook" + diff --git a/knife/spec/data/cookbooks/angrybash/metadata.rb b/knife/spec/data/cookbooks/angrybash/metadata.rb new file mode 100644 index 0000000000..720b8085be --- /dev/null +++ b/knife/spec/data/cookbooks/angrybash/metadata.rb @@ -0,0 +1,2 @@ +name "angrybash" +version "1.0.0" diff --git a/knife/spec/data/cookbooks/angrybash/recipes/default.rb b/knife/spec/data/cookbooks/angrybash/recipes/default.rb new file mode 100644 index 0000000000..458a29103e --- /dev/null +++ b/knife/spec/data/cookbooks/angrybash/recipes/default.rb @@ -0,0 +1,8 @@ +bash "go off the rails" do + code <<-END + for i in localhost 127.0.0.1 #{Socket.gethostname()} + do + echo "grant all on *.* to root@'$i' identified by 'a_password'; flush privileges;" | mysql -u root -h 127.0.0.1 + done + END +end diff --git a/knife/spec/data/cookbooks/apache2/files/default/apache2_module_conf_generate.pl b/knife/spec/data/cookbooks/apache2/files/default/apache2_module_conf_generate.pl new file mode 100644 index 0000000000..6cce6229c4 --- /dev/null +++ b/knife/spec/data/cookbooks/apache2/files/default/apache2_module_conf_generate.pl @@ -0,0 +1,2 @@ +# apache2_module_conf_generate.pl +# this is just here for show. diff --git a/knife/spec/data/cookbooks/apache2/metadata.json b/knife/spec/data/cookbooks/apache2/metadata.json new file mode 100644 index 0000000000..18f5e50bb3 --- /dev/null +++ b/knife/spec/data/cookbooks/apache2/metadata.json @@ -0,0 +1,33 @@ +{ + "name": "apache2", + "description": "", + "long_description": "", + "maintainer": "", + "maintainer_email": "", + "license": "All rights reserved", + "platforms": { + + }, + "dependencies": { + + }, + "providing": { + + }, + "recipes": { + + }, + "version": "0.0.1", + "source_url": "", + "issues_url": "", + "privacy": false, + "chef_versions": [ + + ], + "ohai_versions": [ + + ], + "gems": [ + + ] +} diff --git a/knife/spec/data/cookbooks/apache2/metadata.rb b/knife/spec/data/cookbooks/apache2/metadata.rb new file mode 100644 index 0000000000..1273d20d79 --- /dev/null +++ b/knife/spec/data/cookbooks/apache2/metadata.rb @@ -0,0 +1,2 @@ +name "apache2" +version "0.0.1" diff --git a/knife/spec/data/cookbooks/apache2/recipes/default.rb b/knife/spec/data/cookbooks/apache2/recipes/default.rb new file mode 100644 index 0000000000..c2fa53be32 --- /dev/null +++ b/knife/spec/data/cookbooks/apache2/recipes/default.rb @@ -0,0 +1,3 @@ +# +# Nothing ot see here +#
\ No newline at end of file diff --git a/knife/spec/data/cookbooks/borken/metadata.rb b/knife/spec/data/cookbooks/borken/metadata.rb new file mode 100644 index 0000000000..58700b2d8e --- /dev/null +++ b/knife/spec/data/cookbooks/borken/metadata.rb @@ -0,0 +1,2 @@ +name "borken" +version "1.0.0" diff --git a/knife/spec/data/cookbooks/borken/recipes/default.rb b/knife/spec/data/cookbooks/borken/recipes/default.rb new file mode 100644 index 0000000000..caf40b3974 --- /dev/null +++ b/knife/spec/data/cookbooks/borken/recipes/default.rb @@ -0,0 +1,2 @@ +a cat walked on the keyboard one day... +(*&(*&(*&(*&(*^%$%^%#^^&(*)(*{}}}}}}}}+++++===))))))
\ No newline at end of file diff --git a/knife/spec/data/cookbooks/borken/templates/default/borken.erb b/knife/spec/data/cookbooks/borken/templates/default/borken.erb new file mode 100644 index 0000000000..cbb32c1cd7 --- /dev/null +++ b/knife/spec/data/cookbooks/borken/templates/default/borken.erb @@ -0,0 +1,2 @@ +a cat walked on the keyboard one day... +<%= (*&)(*^^^^*******++_+_--- }}}}]]]end)%>
\ No newline at end of file diff --git a/knife/spec/data/cookbooks/chefignore b/knife/spec/data/cookbooks/chefignore new file mode 100644 index 0000000000..84b4f1e99f --- /dev/null +++ b/knife/spec/data/cookbooks/chefignore @@ -0,0 +1,8 @@ +# +# The ignore file allows you to skip files in cookbooks with the same name that appear +# later in the search path. +# + +recipes/ignoreme.rb + # comments can be indented +ignored diff --git a/knife/spec/data/cookbooks/ignorken/files/default/not_me.rb b/knife/spec/data/cookbooks/ignorken/files/default/not_me.rb new file mode 100644 index 0000000000..8063e32a95 --- /dev/null +++ b/knife/spec/data/cookbooks/ignorken/files/default/not_me.rb @@ -0,0 +1,2 @@ +a cat walked on the keyboard one day... +(*&(*&(*&(*&(*^%$%^%#^^&(*)(*{}}}}}}}}+++++===)))))) diff --git a/knife/spec/data/cookbooks/ignorken/metadata.rb b/knife/spec/data/cookbooks/ignorken/metadata.rb new file mode 100644 index 0000000000..f91d92de32 --- /dev/null +++ b/knife/spec/data/cookbooks/ignorken/metadata.rb @@ -0,0 +1,2 @@ +name "ignorken" +version "1.0.0" diff --git a/knife/spec/data/cookbooks/ignorken/recipes/default.rb b/knife/spec/data/cookbooks/ignorken/recipes/default.rb new file mode 100644 index 0000000000..0f3b3a5d8d --- /dev/null +++ b/knife/spec/data/cookbooks/ignorken/recipes/default.rb @@ -0,0 +1 @@ +# This is fine!
\ No newline at end of file diff --git a/knife/spec/data/cookbooks/ignorken/recipes/ignoreme.rb b/knife/spec/data/cookbooks/ignorken/recipes/ignoreme.rb new file mode 100644 index 0000000000..caf40b3974 --- /dev/null +++ b/knife/spec/data/cookbooks/ignorken/recipes/ignoreme.rb @@ -0,0 +1,2 @@ +a cat walked on the keyboard one day... +(*&(*&(*&(*&(*^%$%^%#^^&(*)(*{}}}}}}}}+++++===))))))
\ No newline at end of file diff --git a/knife/spec/data/cookbooks/ignorken/templates/ubuntu-12.10/not_me.rb b/knife/spec/data/cookbooks/ignorken/templates/ubuntu-12.10/not_me.rb new file mode 100644 index 0000000000..8063e32a95 --- /dev/null +++ b/knife/spec/data/cookbooks/ignorken/templates/ubuntu-12.10/not_me.rb @@ -0,0 +1,2 @@ +a cat walked on the keyboard one day... +(*&(*&(*&(*&(*^%$%^%#^^&(*)(*{}}}}}}}}+++++===)))))) diff --git a/knife/spec/data/cookbooks/irssi/files/default/irssi.response b/knife/spec/data/cookbooks/irssi/files/default/irssi.response new file mode 100644 index 0000000000..6b67a12758 --- /dev/null +++ b/knife/spec/data/cookbooks/irssi/files/default/irssi.response @@ -0,0 +1,2 @@ +# Hi, I'm pretending to be the preseed file for installing the irssi on debian +# or Ubuntu diff --git a/knife/spec/data/cookbooks/java/files/default/java.response b/knife/spec/data/cookbooks/java/files/default/java.response new file mode 100644 index 0000000000..eb4aa3c124 --- /dev/null +++ b/knife/spec/data/cookbooks/java/files/default/java.response @@ -0,0 +1,2 @@ +# Hi, I'm pretending to be the preseed file for installing the Sun JDK on debian +# or Ubuntu
\ No newline at end of file diff --git a/knife/spec/data/cookbooks/java/metadata.json b/knife/spec/data/cookbooks/java/metadata.json new file mode 100644 index 0000000000..9d46842f3c --- /dev/null +++ b/knife/spec/data/cookbooks/java/metadata.json @@ -0,0 +1,33 @@ +{ + "name": "java", + "description": "", + "long_description": "", + "maintainer": "", + "maintainer_email": "", + "license": "All rights reserved", + "platforms": { + + }, + "dependencies": { + + }, + "providing": { + + }, + "recipes": { + + }, + "version": "0.0.1", + "source_url": "", + "issues_url": "", + "privacy": false, + "chef_versions": [ + + ], + "ohai_versions": [ + + ], + "gems": [ + + ] +} diff --git a/knife/spec/data/cookbooks/java/metadata.rb b/knife/spec/data/cookbooks/java/metadata.rb new file mode 100644 index 0000000000..7c5585304c --- /dev/null +++ b/knife/spec/data/cookbooks/java/metadata.rb @@ -0,0 +1,2 @@ +name "java" +version "0.0.1" diff --git a/knife/spec/data/cookbooks/name-mismatch-versionnumber/README.md b/knife/spec/data/cookbooks/name-mismatch-versionnumber/README.md new file mode 100644 index 0000000000..a61dc9a390 --- /dev/null +++ b/knife/spec/data/cookbooks/name-mismatch-versionnumber/README.md @@ -0,0 +1,4 @@ +# name-mismatch + +TODO: Enter the cookbook description here. + diff --git a/knife/spec/data/cookbooks/name-mismatch-versionnumber/metadata.rb b/knife/spec/data/cookbooks/name-mismatch-versionnumber/metadata.rb new file mode 100644 index 0000000000..81775bdcc8 --- /dev/null +++ b/knife/spec/data/cookbooks/name-mismatch-versionnumber/metadata.rb @@ -0,0 +1,8 @@ +name 'name-mismatch' +maintainer '' +maintainer_email '' +license '' +description 'Installs/Configures name-mismatch' +long_description 'Installs/Configures name-mismatch' +version '0.1.0' + diff --git a/knife/spec/data/cookbooks/name-mismatch-versionnumber/recipes/default.rb b/knife/spec/data/cookbooks/name-mismatch-versionnumber/recipes/default.rb new file mode 100644 index 0000000000..0bb34e78e1 --- /dev/null +++ b/knife/spec/data/cookbooks/name-mismatch-versionnumber/recipes/default.rb @@ -0,0 +1,8 @@ +# +# Cookbook Name:: name-mismatch +# Recipe:: default +# +# Copyright 2014-2016, +# +# +# diff --git a/knife/spec/data/cookbooks/openldap/.root_dotfile b/knife/spec/data/cookbooks/openldap/.root_dotfile new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/.root_dotfile diff --git a/knife/spec/data/cookbooks/openldap/attributes/default.rb b/knife/spec/data/cookbooks/openldap/attributes/default.rb new file mode 100644 index 0000000000..d0756f17e3 --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/attributes/default.rb @@ -0,0 +1,16 @@ +chef_env ||= nil +case chef_env +when "prod" + default[:ldap_server] = "ops1prod" + default[:ldap_basedn] = "dc=hjksolutions,dc=com" + default[:ldap_replication_password] = "yes" +when "corp" + default[:ldap_server] = "ops1prod" + default[:ldap_basedn] = "dc=hjksolutions,dc=com" + default[:ldap_replication_password] = "yougotit" +else + + default[:ldap_server] = "ops1prod" + default[:ldap_basedn] = "dc=hjksolutions,dc=com" + default[:ldap_replication_password] = "forsure" +end diff --git a/knife/spec/data/cookbooks/openldap/attributes/smokey.rb b/knife/spec/data/cookbooks/openldap/attributes/smokey.rb new file mode 100644 index 0000000000..4489c6a7ac --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/attributes/smokey.rb @@ -0,0 +1 @@ +default[:smokey] = "robinson" diff --git a/knife/spec/data/cookbooks/openldap/definitions/client.rb b/knife/spec/data/cookbooks/openldap/definitions/client.rb new file mode 100644 index 0000000000..ac81831d11 --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/definitions/client.rb @@ -0,0 +1,5 @@ +define :openldap_client, :mothra => "a big monster" do + cat "#{params[:name]}" do + pretty_kitty true + end +end diff --git a/knife/spec/data/cookbooks/openldap/definitions/server.rb b/knife/spec/data/cookbooks/openldap/definitions/server.rb new file mode 100644 index 0000000000..2df437aa84 --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/definitions/server.rb @@ -0,0 +1,5 @@ +define :openldap_server, :mothra => "a big monster" do + cat "#{params[:name]}" do + pretty_kitty true + end +end diff --git a/knife/spec/data/cookbooks/openldap/files/default/.dotfile b/knife/spec/data/cookbooks/openldap/files/default/.dotfile new file mode 100644 index 0000000000..35ae928f91 --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/files/default/.dotfile @@ -0,0 +1 @@ +I am here to test .dotfiles work in file directories. diff --git a/knife/spec/data/cookbooks/openldap/files/default/.ssh/id_rsa b/knife/spec/data/cookbooks/openldap/files/default/.ssh/id_rsa new file mode 100644 index 0000000000..20a3ea410a --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/files/default/.ssh/id_rsa @@ -0,0 +1 @@ +FAKE KEY
\ No newline at end of file diff --git a/knife/spec/data/cookbooks/openldap/files/default/remotedir/.a_dotdir/.a_dotfile_in_a_dotdir b/knife/spec/data/cookbooks/openldap/files/default/remotedir/.a_dotdir/.a_dotfile_in_a_dotdir new file mode 100644 index 0000000000..f44a956c15 --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/files/default/remotedir/.a_dotdir/.a_dotfile_in_a_dotdir @@ -0,0 +1 @@ +this is a dotfile in a dotdir diff --git a/knife/spec/data/cookbooks/openldap/files/default/remotedir/not_a_template.erb b/knife/spec/data/cookbooks/openldap/files/default/remotedir/not_a_template.erb new file mode 100644 index 0000000000..fc68ce83a1 --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/files/default/remotedir/not_a_template.erb @@ -0,0 +1,2 @@ +# This file is not a chef template despite being and erb. +# It should not be included in a cookbook syntax check diff --git a/knife/spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file1.txt b/knife/spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file1.txt new file mode 100644 index 0000000000..7632730912 --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file1.txt @@ -0,0 +1,3 @@ +# remote directory +# file specificity: default +# relpath: remotedir/remote_dir_file1.txt
\ No newline at end of file diff --git a/knife/spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file2.txt b/knife/spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file2.txt new file mode 100644 index 0000000000..fab0433922 --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file2.txt @@ -0,0 +1,3 @@ +# remote directory +# file specificity: default +# relpath: remotedir/remote_dir_file2.txt
\ No newline at end of file diff --git a/knife/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/.a_dotfile b/knife/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/.a_dotfile new file mode 100644 index 0000000000..9a2b2a4b5f --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/.a_dotfile @@ -0,0 +1 @@ +this is a file with a name beginning with a . dot diff --git a/knife/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/remote_subdir_file1.txt b/knife/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/remote_subdir_file1.txt new file mode 100644 index 0000000000..611294cb81 --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/remote_subdir_file1.txt @@ -0,0 +1,3 @@ +# remote directory +# file specificity: default +# relpath: remotedir/remotesubdir/remote_dir_file1.txt
\ No newline at end of file diff --git a/knife/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/remote_subdir_file2.txt b/knife/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/remote_subdir_file2.txt new file mode 100644 index 0000000000..e0396542a4 --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/remote_subdir_file2.txt @@ -0,0 +1,3 @@ +# remote directory +# file specificity: default +# relpath: remotedir/remotesubdir/remote_dir_file2.txt
\ No newline at end of file diff --git a/knife/spec/data/cookbooks/openldap/files/default/remotedir/subdir_with_no_file_just_a_subsubdir/the_subsubdir/some_file.txt b/knife/spec/data/cookbooks/openldap/files/default/remotedir/subdir_with_no_file_just_a_subsubdir/the_subsubdir/some_file.txt new file mode 100644 index 0000000000..bc47369aad --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/files/default/remotedir/subdir_with_no_file_just_a_subsubdir/the_subsubdir/some_file.txt @@ -0,0 +1,3 @@ +# remote directory +# file specificity: default +# relpath: remotedir/subdir_with_no_file_just_a_subsubdir/the_subsubdir/some_file.txt diff --git a/knife/spec/data/cookbooks/openldap/libraries/openldap.rb b/knife/spec/data/cookbooks/openldap/libraries/openldap.rb new file mode 100644 index 0000000000..0b9389c688 --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/libraries/openldap.rb @@ -0,0 +1,4 @@ +require_relative './openldap/version' + +class OpenLDAP +end diff --git a/knife/spec/data/cookbooks/openldap/libraries/openldap/version.rb b/knife/spec/data/cookbooks/openldap/libraries/openldap/version.rb new file mode 100644 index 0000000000..4bff12b01c --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/libraries/openldap/version.rb @@ -0,0 +1,3 @@ +class OpenLDAP + VERSION = '8.9.10' +end diff --git a/knife/spec/data/cookbooks/openldap/metadata.rb b/knife/spec/data/cookbooks/openldap/metadata.rb new file mode 100644 index 0000000000..fc132946f2 --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/metadata.rb @@ -0,0 +1,8 @@ +name "openldap" +maintainer "Chef Software, Inc." +maintainer_email "cookbooks@chef.io" +license "Apache 2.0" +description "Installs and configures all aspects of openldap using Debian style symlinks with helper definitions" +long_description "The long description for the openldap cookbook from metadata.rb" +version "8.9.10" +recipe "openldap", "Main Open LDAP configuration" diff --git a/knife/spec/data/cookbooks/openldap/recipes/default.rb b/knife/spec/data/cookbooks/openldap/recipes/default.rb new file mode 100644 index 0000000000..ba5c9d1507 --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/recipes/default.rb @@ -0,0 +1,4 @@ + +cat "blanket" do + pretty_kitty true +end diff --git a/knife/spec/data/cookbooks/openldap/recipes/gigantor.rb b/knife/spec/data/cookbooks/openldap/recipes/gigantor.rb new file mode 100644 index 0000000000..b450eca7cd --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/recipes/gigantor.rb @@ -0,0 +1,3 @@ +cat "blanket" do + pretty_kitty false +end diff --git a/knife/spec/data/cookbooks/openldap/recipes/one.rb b/knife/spec/data/cookbooks/openldap/recipes/one.rb new file mode 100644 index 0000000000..d2d3cfd409 --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/recipes/one.rb @@ -0,0 +1,15 @@ +## +# Nodes should have a unique name +## +name "test.example.com-default" + +## +# Nodes can set arbitrary arguments +## +sunshine "in" +something "else" + +## +# Nodes should have recipes +## +recipes "operations-master", "operations-monitoring" diff --git a/knife/spec/data/cookbooks/openldap/recipes/return.rb b/knife/spec/data/cookbooks/openldap/recipes/return.rb new file mode 100644 index 0000000000..79bfb5e441 --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/recipes/return.rb @@ -0,0 +1,2 @@ +# CHEF-5199 regression test. +return nil diff --git a/knife/spec/data/cookbooks/openldap/spec/spec_helper.rb b/knife/spec/data/cookbooks/openldap/spec/spec_helper.rb new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/spec/spec_helper.rb diff --git a/knife/spec/data/cookbooks/openldap/templates/default/all_windows_line_endings.erb b/knife/spec/data/cookbooks/openldap/templates/default/all_windows_line_endings.erb new file mode 100644 index 0000000000..727db12387 --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/templates/default/all_windows_line_endings.erb @@ -0,0 +1,4 @@ +Template rendering libraries
+should support
+different line endings
+
diff --git a/knife/spec/data/cookbooks/openldap/templates/default/helper_test.erb b/knife/spec/data/cookbooks/openldap/templates/default/helper_test.erb new file mode 100644 index 0000000000..92e6fe0427 --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/templates/default/helper_test.erb @@ -0,0 +1 @@ +<%= helper_method %> diff --git a/knife/spec/data/cookbooks/openldap/templates/default/helpers.erb b/knife/spec/data/cookbooks/openldap/templates/default/helpers.erb new file mode 100644 index 0000000000..b973a5287c --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/templates/default/helpers.erb @@ -0,0 +1,14 @@ +<%= @cookbook_name %> +<%= @recipe_name %> +<%= @recipe_line_string %> +<%= @recipe_path %> +<%= @recipe_line %> +<%= @template_name %> +<%= @template_path %> +<%= cookbook_name %> +<%= recipe_name %> +<%= recipe_line_string %> +<%= recipe_path %> +<%= recipe_line %> +<%= template_name %> +<%= template_path %> diff --git a/knife/spec/data/cookbooks/openldap/templates/default/helpers_via_partial_test.erb b/knife/spec/data/cookbooks/openldap/templates/default/helpers_via_partial_test.erb new file mode 100644 index 0000000000..f94b6b5979 --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/templates/default/helpers_via_partial_test.erb @@ -0,0 +1 @@ +<%= render("helper_test.erb").strip %> diff --git a/knife/spec/data/cookbooks/openldap/templates/default/nested_openldap_partials.erb b/knife/spec/data/cookbooks/openldap/templates/default/nested_openldap_partials.erb new file mode 100644 index 0000000000..2d356ec21d --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/templates/default/nested_openldap_partials.erb @@ -0,0 +1 @@ +before <%= render 'nested_partial.erb', :variables => { :hello => @test } %> after diff --git a/knife/spec/data/cookbooks/openldap/templates/default/nested_partial.erb b/knife/spec/data/cookbooks/openldap/templates/default/nested_partial.erb new file mode 100644 index 0000000000..415646ca31 --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/templates/default/nested_partial.erb @@ -0,0 +1 @@ +{<%= @hello %>} diff --git a/knife/spec/data/cookbooks/openldap/templates/default/no_windows_line_endings.erb b/knife/spec/data/cookbooks/openldap/templates/default/no_windows_line_endings.erb new file mode 100644 index 0000000000..e83c03b01a --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/templates/default/no_windows_line_endings.erb @@ -0,0 +1,4 @@ +Template rendering libraries +should support +different line endings + diff --git a/knife/spec/data/cookbooks/openldap/templates/default/openldap_nested_variable_stuff.erb b/knife/spec/data/cookbooks/openldap/templates/default/openldap_nested_variable_stuff.erb new file mode 100644 index 0000000000..5ebee33806 --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/templates/default/openldap_nested_variable_stuff.erb @@ -0,0 +1 @@ +super secret is <%= @secret.first["key"] -%> diff --git a/knife/spec/data/cookbooks/openldap/templates/default/openldap_stuff.conf.erb b/knife/spec/data/cookbooks/openldap/templates/default/openldap_stuff.conf.erb new file mode 100644 index 0000000000..af82f1d96c --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/templates/default/openldap_stuff.conf.erb @@ -0,0 +1 @@ +slappiness is <%= node[:slappiness] -%>
\ No newline at end of file diff --git a/knife/spec/data/cookbooks/openldap/templates/default/openldap_variable_stuff.conf.erb b/knife/spec/data/cookbooks/openldap/templates/default/openldap_variable_stuff.conf.erb new file mode 100644 index 0000000000..e0041c9f99 --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/templates/default/openldap_variable_stuff.conf.erb @@ -0,0 +1 @@ +super secret is <%= @secret -%> diff --git a/knife/spec/data/cookbooks/openldap/templates/default/some_windows_line_endings.erb b/knife/spec/data/cookbooks/openldap/templates/default/some_windows_line_endings.erb new file mode 100644 index 0000000000..fa25999b6a --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/templates/default/some_windows_line_endings.erb @@ -0,0 +1,4 @@ +Template rendering libraries +should support
+different line endings +
diff --git a/knife/spec/data/cookbooks/openldap/templates/default/test.erb b/knife/spec/data/cookbooks/openldap/templates/default/test.erb new file mode 100644 index 0000000000..f39fa7da89 --- /dev/null +++ b/knife/spec/data/cookbooks/openldap/templates/default/test.erb @@ -0,0 +1 @@ +We could be diving for pearls! diff --git a/knife/spec/data/cookbooks/preseed/files/default/preseed-file.seed b/knife/spec/data/cookbooks/preseed/files/default/preseed-file.seed new file mode 100644 index 0000000000..164da3495d --- /dev/null +++ b/knife/spec/data/cookbooks/preseed/files/default/preseed-file.seed @@ -0,0 +1 @@ +chef-integration-test chef-integration-test/sample-var string "hello world" diff --git a/knife/spec/data/cookbooks/preseed/files/default/preseed-template.seed b/knife/spec/data/cookbooks/preseed/files/default/preseed-template.seed new file mode 100644 index 0000000000..011bc40f1b --- /dev/null +++ b/knife/spec/data/cookbooks/preseed/files/default/preseed-template.seed @@ -0,0 +1,4 @@ +# This file should never be executed by the preseeding tests +# This is here to verify that templates are preferred over cookbook_files when +# preseeding packages. +chef-integration-test chef-integration-test/sample-var string "WRONG-cookbook file used instead of template!" diff --git a/knife/spec/data/cookbooks/preseed/metadata.rb b/knife/spec/data/cookbooks/preseed/metadata.rb new file mode 100644 index 0000000000..a48deec08b --- /dev/null +++ b/knife/spec/data/cookbooks/preseed/metadata.rb @@ -0,0 +1,2 @@ +name "preseed" +version "1.0.0" diff --git a/knife/spec/data/cookbooks/preseed/templates/default/preseed-template-variables.seed b/knife/spec/data/cookbooks/preseed/templates/default/preseed-template-variables.seed new file mode 100644 index 0000000000..0df0015f05 --- /dev/null +++ b/knife/spec/data/cookbooks/preseed/templates/default/preseed-template-variables.seed @@ -0,0 +1 @@ +chef-integration-test chef-integration-test/sample-var string "<%= @template_variable -%>" diff --git a/knife/spec/data/cookbooks/preseed/templates/default/preseed-template.seed b/knife/spec/data/cookbooks/preseed/templates/default/preseed-template.seed new file mode 100644 index 0000000000..6229ac83f5 --- /dev/null +++ b/knife/spec/data/cookbooks/preseed/templates/default/preseed-template.seed @@ -0,0 +1 @@ +chef-integration-test chef-integration-test/sample-var string "<%= node[:preseed_value] -%>" diff --git a/knife/spec/data/cookbooks/starter/chefignore b/knife/spec/data/cookbooks/starter/chefignore new file mode 100644 index 0000000000..b9d6c768c2 --- /dev/null +++ b/knife/spec/data/cookbooks/starter/chefignore @@ -0,0 +1,8 @@ +# +# The ignore file allows you to skip files in cookbooks with the same name that appear +# later in the search path. +# + +recipes/default.rb + # comments can be indented +ignored diff --git a/knife/spec/data/cookbooks/starter/files/sample.txt b/knife/spec/data/cookbooks/starter/files/sample.txt new file mode 100644 index 0000000000..e635a0f018 --- /dev/null +++ b/knife/spec/data/cookbooks/starter/files/sample.txt @@ -0,0 +1 @@ +This is a Chef cookbook file. It is used to copy content verbatim on to a server.
\ No newline at end of file diff --git a/knife/spec/data/cookbooks/starter/metadata.rb b/knife/spec/data/cookbooks/starter/metadata.rb new file mode 100644 index 0000000000..fbd288e9c4 --- /dev/null +++ b/knife/spec/data/cookbooks/starter/metadata.rb @@ -0,0 +1,2 @@ +name "starter" +version "1.0.0" diff --git a/knife/spec/data/cookbooks/starter/recipes/default.rb b/knife/spec/data/cookbooks/starter/recipes/default.rb new file mode 100644 index 0000000000..4b5f712879 --- /dev/null +++ b/knife/spec/data/cookbooks/starter/recipes/default.rb @@ -0,0 +1,4 @@ +# This is a Chef recipe file. It can be used to specify resources which will +# apply configuration to a server. + +# For more information, see the documentation: https://docs.chef.io/recipes/ diff --git a/knife/spec/data/cookbooks/supports-platform-constraints/metadata.rb b/knife/spec/data/cookbooks/supports-platform-constraints/metadata.rb new file mode 100644 index 0000000000..3620249d5f --- /dev/null +++ b/knife/spec/data/cookbooks/supports-platform-constraints/metadata.rb @@ -0,0 +1,5 @@ +name 'supports-platform-constraints' +version '0.1.0' + +supports 'centos', '>= 6' +supports 'freebsd', '> 10.1-fake-p12' diff --git a/knife/spec/data/cookbooks/wget/files/default/wget.response b/knife/spec/data/cookbooks/wget/files/default/wget.response new file mode 100644 index 0000000000..b5f22f4d10 --- /dev/null +++ b/knife/spec/data/cookbooks/wget/files/default/wget.response @@ -0,0 +1,2 @@ +# Hi, I'm pretending to be the preseed file for installing the wget on debian +# or Ubuntu diff --git a/knife/spec/data/definitions/test.rb b/knife/spec/data/definitions/test.rb new file mode 100644 index 0000000000..b0d0effc27 --- /dev/null +++ b/knife/spec/data/definitions/test.rb @@ -0,0 +1,5 @@ +define :rico_suave, :rich => "smooth" do + zen_master "test" do + something "#{params[:rich]}" + end +end
\ No newline at end of file diff --git a/knife/spec/data/dsc_lcm.pfx b/knife/spec/data/dsc_lcm.pfx Binary files differnew file mode 100644 index 0000000000..3912ed3753 --- /dev/null +++ b/knife/spec/data/dsc_lcm.pfx diff --git a/knife/spec/data/environment-config.rb b/knife/spec/data/environment-config.rb new file mode 100644 index 0000000000..a157ecf0ea --- /dev/null +++ b/knife/spec/data/environment-config.rb @@ -0,0 +1,5 @@ +# +# Sample Chef Config File +# + +environment "production"
\ No newline at end of file diff --git a/knife/spec/data/file-providers-method-snapshot-chef-11-4.json b/knife/spec/data/file-providers-method-snapshot-chef-11-4.json new file mode 100644 index 0000000000..f6695aacd6 --- /dev/null +++ b/knife/spec/data/file-providers-method-snapshot-chef-11-4.json @@ -0,0 +1,127 @@ +{ + "Chef::Provider::CookbookFile": [ + "action_create", + "file_cache_location", + "resource_cookbook", + "backup_new_resource", + "content_stale?", + "diff_current_from_content", + "is_binary?", + "diff_current", + "setup_acl", + "compare_content", + "set_content", + "update_new_file_state", + "set_all_access_controls", + "action_create_if_missing", + "action_delete", + "action_touch", + "backup", + "deploy_tempfile", + "shell_out", + "shell_out!", + "run_command_compatible_options", + "checksum", + "access_controls", + "enforce_ownership_and_permissions" + ], + "Chef::Provider::RemoteFile": [ + "action_create", + "current_resource_matches_target_checksum?", + "matches_current_checksum?", + "backup_new_resource", + "source_file", + "http_client_opts", + "diff_current_from_content", + "is_binary?", + "diff_current", + "setup_acl", + "compare_content", + "set_content", + "update_new_file_state", + "set_all_access_controls", + "action_create_if_missing", + "action_delete", + "action_touch", + "backup", + "deploy_tempfile", + "shell_out", + "shell_out!", + "run_command_compatible_options", + "checksum", + "access_controls", + "enforce_ownership_and_permissions" + ], + "Chef::Provider::Template": [ + "action_create", + "template_finder", + "template_location", + "resource_cookbook", + "rendered", + "content_matches?", + "render_template", + "diff_current_from_content", + "is_binary?", + "diff_current", + "setup_acl", + "compare_content", + "set_content", + "update_new_file_state", + "set_all_access_controls", + "action_create_if_missing", + "action_delete", + "action_touch", + "backup", + "deploy_tempfile", + "shell_out", + "shell_out!", + "run_command_compatible_options", + "checksum", + "access_controls", + "enforce_ownership_and_permissions" + ], + "Chef::Provider::Directory": [ + "action_create", + "action_delete", + "diff_current_from_content", + "is_binary?", + "diff_current", + "setup_acl", + "compare_content", + "set_content", + "update_new_file_state", + "set_all_access_controls", + "action_create_if_missing", + "action_touch", + "backup", + "deploy_tempfile", + "shell_out", + "shell_out!", + "run_command_compatible_options", + "checksum", + "access_controls", + "enforce_ownership_and_permissions" + ], + "Chef::Provider::File": [ + "diff_current_from_content", + "is_binary?", + "diff_current", + "setup_acl", + "compare_content", + "set_content", + "update_new_file_state", + "action_create", + "set_all_access_controls", + "action_create_if_missing", + "action_delete", + "action_touch", + "backup", + "deploy_tempfile", + "shell_out", + "shell_out!", + "run_command_compatible_options", + "checksum", + "access_controls", + "enforce_ownership_and_permissions" + ] +} diff --git a/knife/spec/data/fileedit/blank b/knife/spec/data/fileedit/blank new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/knife/spec/data/fileedit/blank diff --git a/knife/spec/data/fileedit/hosts b/knife/spec/data/fileedit/hosts new file mode 100644 index 0000000000..6fbdc0f59d --- /dev/null +++ b/knife/spec/data/fileedit/hosts @@ -0,0 +1,4 @@ +127.0.0.1 localhost +255.255.255.255 broadcasthost +::1 localhost +fe80::1%lo0 localhost diff --git a/knife/spec/data/gems/chef-integration-test-0.1.0.gem b/knife/spec/data/gems/chef-integration-test-0.1.0.gem Binary files differnew file mode 100644 index 0000000000..bcf1c77fe2 --- /dev/null +++ b/knife/spec/data/gems/chef-integration-test-0.1.0.gem diff --git a/knife/spec/data/git_bundles/example-repo.gitbundle b/knife/spec/data/git_bundles/example-repo.gitbundle Binary files differnew file mode 100644 index 0000000000..de08296dc3 --- /dev/null +++ b/knife/spec/data/git_bundles/example-repo.gitbundle diff --git a/knife/spec/data/git_bundles/sinatra-test-app-with-callback-files.gitbundle b/knife/spec/data/git_bundles/sinatra-test-app-with-callback-files.gitbundle Binary files differnew file mode 100644 index 0000000000..24e36f7eeb --- /dev/null +++ b/knife/spec/data/git_bundles/sinatra-test-app-with-callback-files.gitbundle diff --git a/knife/spec/data/git_bundles/sinatra-test-app-with-symlinks.gitbundle b/knife/spec/data/git_bundles/sinatra-test-app-with-symlinks.gitbundle Binary files differnew file mode 100644 index 0000000000..0a96fbb24f --- /dev/null +++ b/knife/spec/data/git_bundles/sinatra-test-app-with-symlinks.gitbundle diff --git a/knife/spec/data/git_bundles/sinatra-test-app.gitbundle b/knife/spec/data/git_bundles/sinatra-test-app.gitbundle Binary files differnew file mode 100644 index 0000000000..43c54eae5c --- /dev/null +++ b/knife/spec/data/git_bundles/sinatra-test-app.gitbundle diff --git a/knife/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/README.md b/knife/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/README.md new file mode 100644 index 0000000000..3ac2a4f90c --- /dev/null +++ b/knife/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/README.md @@ -0,0 +1,4 @@ +# incomplete-metadata + +TODO: Enter the cookbook description here. + diff --git a/knife/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/metadata.rb b/knife/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/metadata.rb new file mode 100644 index 0000000000..50284be3dd --- /dev/null +++ b/knife/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/metadata.rb @@ -0,0 +1,13 @@ +# ## INCOMPLETE METADATA ## +# This cookbook is invalid b/c it does not set the `name' of the cookbook. + +# Commented out for illustrative purposes +# name 'incomplete-metadata' + +maintainer '' +maintainer_email '' +license '' +description 'Installs/Configures incomplete-metadata' +long_description 'Installs/Configures incomplete-metadata' +version '0.1.0' + diff --git a/knife/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/recipes/default.rb b/knife/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/recipes/default.rb new file mode 100644 index 0000000000..0d8cca1bda --- /dev/null +++ b/knife/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/recipes/default.rb @@ -0,0 +1,8 @@ +# +# Cookbook Name:: incomplete-metadata +# Recipe:: default +# +# Copyright 2014-2016, +# +# +# diff --git a/knife/spec/data/invalid-metadata-chef-repo/invalid-metadata/README.md b/knife/spec/data/invalid-metadata-chef-repo/invalid-metadata/README.md new file mode 100644 index 0000000000..a41867f17c --- /dev/null +++ b/knife/spec/data/invalid-metadata-chef-repo/invalid-metadata/README.md @@ -0,0 +1,4 @@ +# invalid-metadata + +TODO: Enter the cookbook description here. + diff --git a/knife/spec/data/invalid-metadata-chef-repo/invalid-metadata/metadata.rb b/knife/spec/data/invalid-metadata-chef-repo/invalid-metadata/metadata.rb new file mode 100644 index 0000000000..1130ee40e8 --- /dev/null +++ b/knife/spec/data/invalid-metadata-chef-repo/invalid-metadata/metadata.rb @@ -0,0 +1,9 @@ +name 'invalid-metadata' +maintainer '' +maintainer_email '' +license '' +description 'Installs/Configures invalid-metadata' +long_description 'Installs/Configures invalid-metadata' +version '0.1.0' + +raise "THIS METADATA HAS A BUG" diff --git a/knife/spec/data/invalid-metadata-chef-repo/invalid-metadata/recipes/default.rb b/knife/spec/data/invalid-metadata-chef-repo/invalid-metadata/recipes/default.rb new file mode 100644 index 0000000000..33ec1144fe --- /dev/null +++ b/knife/spec/data/invalid-metadata-chef-repo/invalid-metadata/recipes/default.rb @@ -0,0 +1,8 @@ +# +# Cookbook Name:: invalid-metadata +# Recipe:: default +# +# Copyright 2014-2016, +# +# +# diff --git a/knife/spec/data/kitchen/chefignore b/knife/spec/data/kitchen/chefignore new file mode 100644 index 0000000000..a90dc15ebe --- /dev/null +++ b/knife/spec/data/kitchen/chefignore @@ -0,0 +1,6 @@ +# +# The ignore file allows you to skip files in cookbooks with the same name that appear +# later in the search path. +# + +recipes/ignoreme\.rb diff --git a/knife/spec/data/kitchen/openldap/attributes/default.rb b/knife/spec/data/kitchen/openldap/attributes/default.rb new file mode 100644 index 0000000000..d208959475 --- /dev/null +++ b/knife/spec/data/kitchen/openldap/attributes/default.rb @@ -0,0 +1,3 @@ +# +# Nothing to see here, move along +# diff --git a/knife/spec/data/kitchen/openldap/attributes/robinson.rb b/knife/spec/data/kitchen/openldap/attributes/robinson.rb new file mode 100644 index 0000000000..9d6b44d464 --- /dev/null +++ b/knife/spec/data/kitchen/openldap/attributes/robinson.rb @@ -0,0 +1,3 @@ +# +# Smokey lives here +#
\ No newline at end of file diff --git a/knife/spec/data/kitchen/openldap/definitions/client.rb b/knife/spec/data/kitchen/openldap/definitions/client.rb new file mode 100644 index 0000000000..d4c2263b54 --- /dev/null +++ b/knife/spec/data/kitchen/openldap/definitions/client.rb @@ -0,0 +1,3 @@ +# +# A sad client +# diff --git a/knife/spec/data/kitchen/openldap/definitions/drewbarrymore.rb b/knife/spec/data/kitchen/openldap/definitions/drewbarrymore.rb new file mode 100644 index 0000000000..510f0c35da --- /dev/null +++ b/knife/spec/data/kitchen/openldap/definitions/drewbarrymore.rb @@ -0,0 +1,3 @@ +# +# Was in people magazine this month... +#
\ No newline at end of file diff --git a/knife/spec/data/kitchen/openldap/recipes/gigantor.rb b/knife/spec/data/kitchen/openldap/recipes/gigantor.rb new file mode 100644 index 0000000000..70a41960eb --- /dev/null +++ b/knife/spec/data/kitchen/openldap/recipes/gigantor.rb @@ -0,0 +1,3 @@ +cat "blanket" do + pretty_kitty true +end
\ No newline at end of file diff --git a/knife/spec/data/kitchen/openldap/recipes/ignoreme.rb b/knife/spec/data/kitchen/openldap/recipes/ignoreme.rb new file mode 100644 index 0000000000..15095986c6 --- /dev/null +++ b/knife/spec/data/kitchen/openldap/recipes/ignoreme.rb @@ -0,0 +1,3 @@ +# +# this file will never be seen +#
\ No newline at end of file diff --git a/knife/spec/data/kitchen/openldap/recipes/woot.rb b/knife/spec/data/kitchen/openldap/recipes/woot.rb new file mode 100644 index 0000000000..44893dae36 --- /dev/null +++ b/knife/spec/data/kitchen/openldap/recipes/woot.rb @@ -0,0 +1,3 @@ +# +# Such a funny word.. +# diff --git a/knife/spec/data/knife-home/.chef/plugins/knife/example_home_subcommand.rb b/knife/spec/data/knife-home/.chef/plugins/knife/example_home_subcommand.rb new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/knife/spec/data/knife-home/.chef/plugins/knife/example_home_subcommand.rb diff --git a/knife/spec/data/knife-site-subcommands/plugins/knife/example_subcommand.rb b/knife/spec/data/knife-site-subcommands/plugins/knife/example_subcommand.rb new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/knife/spec/data/knife-site-subcommands/plugins/knife/example_subcommand.rb diff --git a/knife/spec/data/knife_subcommand/test_explicit_category.rb b/knife/spec/data/knife_subcommand/test_explicit_category.rb new file mode 100644 index 0000000000..96d50691a1 --- /dev/null +++ b/knife/spec/data/knife_subcommand/test_explicit_category.rb @@ -0,0 +1,7 @@ +module KnifeSpecs + class TestExplicitCategory < Chef::Knife + # i.e., the cookbook site commands should be in the cookbook site + # category instead of cookbook (which is what would be assumed) + category "cookbook site" + end +end
\ No newline at end of file diff --git a/knife/spec/data/knife_subcommand/test_name_mapping.rb b/knife/spec/data/knife_subcommand/test_name_mapping.rb new file mode 100644 index 0000000000..25c49b0df0 --- /dev/null +++ b/knife/spec/data/knife_subcommand/test_name_mapping.rb @@ -0,0 +1,4 @@ +module KnifeSpecs + class TestNameMapping < Chef::Knife + end +end diff --git a/knife/spec/data/knife_subcommand/test_yourself.rb b/knife/spec/data/knife_subcommand/test_yourself.rb new file mode 100644 index 0000000000..f18f565885 --- /dev/null +++ b/knife/spec/data/knife_subcommand/test_yourself.rb @@ -0,0 +1,21 @@ +module KnifeSpecs + class TestYourself < Chef::Knife + + class << self + attr_reader :test_deps_loaded + end + + deps do + @test_deps_loaded = true + end + + option :scro, :short => '-s SCRO', :long => '--scro SCRO', :description => 'a configurable setting' + + attr_reader :ran + + def run + @ran = true + self # return self so tests can poke at me + end + end +end diff --git a/knife/spec/data/lwrp/providers/buck_passer.rb b/knife/spec/data/lwrp/providers/buck_passer.rb new file mode 100644 index 0000000000..9d764277ce --- /dev/null +++ b/knife/spec/data/lwrp/providers/buck_passer.rb @@ -0,0 +1,28 @@ +provides :buck_passer + +def without_deprecation_warnings(&block) + old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors] + Chef::Config[:treat_deprecation_warnings_as_errors] = false + begin + yield + ensure + Chef::Config[:treat_deprecation_warnings_as_errors] = old_treat_deprecation_warnings_as_errors + end +end + +def action_pass_buck + lwrp_foo :prepared_thumbs do + action :prepare_thumbs + # We know there will be a deprecation error here; head it off + without_deprecation_warnings do + provider :lwrp_thumb_twiddler + end + end + lwrp_foo :twiddled_thumbs do + action :twiddle_thumbs + # We know there will be a deprecation error here; head it off + without_deprecation_warnings do + provider :lwrp_thumb_twiddler + end + end +end diff --git a/knife/spec/data/lwrp/providers/buck_passer_2.rb b/knife/spec/data/lwrp/providers/buck_passer_2.rb new file mode 100644 index 0000000000..0b8f49f7bd --- /dev/null +++ b/knife/spec/data/lwrp/providers/buck_passer_2.rb @@ -0,0 +1,26 @@ +def without_deprecation_warnings(&block) + old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors] + Chef::Config[:treat_deprecation_warnings_as_errors] = false + begin + yield + ensure + Chef::Config[:treat_deprecation_warnings_as_errors] = old_treat_deprecation_warnings_as_errors + end +end + +def action_pass_buck + lwrp_bar :prepared_eyes do + action :prepare_eyes + # We know there will be a deprecation error here; head it off + without_deprecation_warnings do + provider :lwrp_paint_drying_watcher + end + end + lwrp_bar :dried_paint_watched do + action :watch_paint_dry + # We know there will be a deprecation error here; head it off + without_deprecation_warnings do + provider :lwrp_paint_drying_watcher + end + end +end diff --git a/knife/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb b/knife/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb new file mode 100644 index 0000000000..b86dc860d0 --- /dev/null +++ b/knife/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb @@ -0,0 +1,28 @@ +# This action tests that embedded Resources have access to the enclosing Provider's +# lexical scope (as demonstrated by the call to new_resource) and that all parameters +# are passed properly (as demonstrated by the call to generate_new_name). +attr_reader :enclosed_resource + +def without_deprecation_warnings(&block) + old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors] + Chef::Config[:treat_deprecation_warnings_as_errors] = false + begin + yield + ensure + Chef::Config[:treat_deprecation_warnings_as_errors] = old_treat_deprecation_warnings_as_errors + end +end + +def action_twiddle_thumbs + @enclosed_resource = lwrp_foo :foo do + monkey generate_new_name(new_resource.monkey){ 'the monkey' } + # We know there will be a deprecation error here; head it off + without_deprecation_warnings do + provider :lwrp_monkey_name_printer + end + end +end + +def generate_new_name(str, &block) + "#{str}, #{block.call}" +end diff --git a/knife/spec/data/lwrp/providers/inline_compiler.rb b/knife/spec/data/lwrp/providers/inline_compiler.rb new file mode 100644 index 0000000000..91a80b32af --- /dev/null +++ b/knife/spec/data/lwrp/providers/inline_compiler.rb @@ -0,0 +1,24 @@ + +action :test do + + ruby_block "interior-ruby-block-1" do + block do + # doesn't need to do anything + end + notifies :run, "ruby_block[interior-ruby-block-2]", :immediately + end + + ruby_block "interior-ruby-block-2" do + block do + $interior_ruby_block_2 = "executed" + end + action :nothing + end +end + +action :no_updates do + ruby_block "no-action" do + block {} + action :nothing + end +end diff --git a/knife/spec/data/lwrp/providers/monkey_name_printer.rb b/knife/spec/data/lwrp/providers/monkey_name_printer.rb new file mode 100644 index 0000000000..97ca66c65d --- /dev/null +++ b/knife/spec/data/lwrp/providers/monkey_name_printer.rb @@ -0,0 +1,5 @@ +attr_reader :monkey_name + +action :twiddle_thumbs do + @monkey_name = "my monkey's name is '#{new_resource.monkey}'" +end diff --git a/knife/spec/data/lwrp/providers/paint_drying_watcher.rb b/knife/spec/data/lwrp/providers/paint_drying_watcher.rb new file mode 100644 index 0000000000..04b4732dcc --- /dev/null +++ b/knife/spec/data/lwrp/providers/paint_drying_watcher.rb @@ -0,0 +1,7 @@ +action :prepare_eyes do + "Prepared" +end + +action :watch_paint_dry do + "This isn't very interesting" +end diff --git a/knife/spec/data/lwrp/providers/thumb_twiddler.rb b/knife/spec/data/lwrp/providers/thumb_twiddler.rb new file mode 100644 index 0000000000..7f014615db --- /dev/null +++ b/knife/spec/data/lwrp/providers/thumb_twiddler.rb @@ -0,0 +1,7 @@ +action :prepare_thumbs do + "Prepared" +end + +action :twiddle_thumbs do + "Twiddle twiddle" +end diff --git a/knife/spec/data/lwrp/resources/bar.rb b/knife/spec/data/lwrp/resources/bar.rb new file mode 100644 index 0000000000..d36575917d --- /dev/null +++ b/knife/spec/data/lwrp/resources/bar.rb @@ -0,0 +1,4 @@ +unified_mode true + +provides :lwrp_bar # This makes sure that we cover the case of lwrps using provides +actions :pass_buck, :prepare_eyes, :watch_paint_dry diff --git a/knife/spec/data/lwrp/resources/buck_passer.rb b/knife/spec/data/lwrp/resources/buck_passer.rb new file mode 100644 index 0000000000..6f542dc423 --- /dev/null +++ b/knife/spec/data/lwrp/resources/buck_passer.rb @@ -0,0 +1,6 @@ +unified_mode true + +provides :buck_passer + +default_action :pass_buck +actions :pass_buck diff --git a/knife/spec/data/lwrp/resources/buck_passer_2.rb b/knife/spec/data/lwrp/resources/buck_passer_2.rb new file mode 100644 index 0000000000..c0ab7d7885 --- /dev/null +++ b/knife/spec/data/lwrp/resources/buck_passer_2.rb @@ -0,0 +1,4 @@ +unified_mode true + +default_action :pass_buck +actions :pass_buck diff --git a/knife/spec/data/lwrp/resources/embedded_resource_accesses_providers_scope.rb b/knife/spec/data/lwrp/resources/embedded_resource_accesses_providers_scope.rb new file mode 100644 index 0000000000..faece8b582 --- /dev/null +++ b/knife/spec/data/lwrp/resources/embedded_resource_accesses_providers_scope.rb @@ -0,0 +1,4 @@ +unified_mode true + +default_action :twiddle_thumbs +actions :twiddle_thumbs diff --git a/knife/spec/data/lwrp/resources/foo.rb b/knife/spec/data/lwrp/resources/foo.rb new file mode 100644 index 0000000000..d4d2155122 --- /dev/null +++ b/knife/spec/data/lwrp/resources/foo.rb @@ -0,0 +1,6 @@ +unified_mode true + +actions :prepare_thumbs, :twiddle_thumbs +default_action :pass_buck + +attribute :monkey, :kind_of => String diff --git a/knife/spec/data/lwrp/resources/inline_compiler.rb b/knife/spec/data/lwrp/resources/inline_compiler.rb new file mode 100644 index 0000000000..0e63e4ce43 --- /dev/null +++ b/knife/spec/data/lwrp/resources/inline_compiler.rb @@ -0,0 +1,4 @@ +unified_mode true + +default_action :test +actions :test, :no_updates diff --git a/knife/spec/data/lwrp/resources/monkey_name_printer.rb b/knife/spec/data/lwrp/resources/monkey_name_printer.rb new file mode 100644 index 0000000000..60c3203dca --- /dev/null +++ b/knife/spec/data/lwrp/resources/monkey_name_printer.rb @@ -0,0 +1,6 @@ +unified_mode true + +property :monkey + +default_action :twiddle_thumbs +actions :twiddle_thumbs diff --git a/knife/spec/data/lwrp/resources/paint_drying_watcher.rb b/knife/spec/data/lwrp/resources/paint_drying_watcher.rb new file mode 100644 index 0000000000..1bff7c5b50 --- /dev/null +++ b/knife/spec/data/lwrp/resources/paint_drying_watcher.rb @@ -0,0 +1,4 @@ +unified_mode true + +default_action :prepare_eyes +actions :prepare_eyes, :watch_paint_dry diff --git a/knife/spec/data/lwrp/resources/thumb_twiddler.rb b/knife/spec/data/lwrp/resources/thumb_twiddler.rb new file mode 100644 index 0000000000..f4ba71cebc --- /dev/null +++ b/knife/spec/data/lwrp/resources/thumb_twiddler.rb @@ -0,0 +1,4 @@ +unified_mode true + +default_action :prepare_thumbs +actions :prepare_thumbs, :twiddle_thumbs diff --git a/knife/spec/data/lwrp/resources_with_default_attributes/nodeattr.rb b/knife/spec/data/lwrp/resources_with_default_attributes/nodeattr.rb new file mode 100644 index 0000000000..ef6b694cb2 --- /dev/null +++ b/knife/spec/data/lwrp/resources_with_default_attributes/nodeattr.rb @@ -0,0 +1,3 @@ +unified_mode true + +attribute :penguin, :kind_of => String, :default => node[:penguin_name] diff --git a/knife/spec/data/lwrp_const_scoping/resources/conflict.rb b/knife/spec/data/lwrp_const_scoping/resources/conflict.rb new file mode 100644 index 0000000000..059460afc0 --- /dev/null +++ b/knife/spec/data/lwrp_const_scoping/resources/conflict.rb @@ -0,0 +1 @@ +unified_mode true diff --git a/knife/spec/data/lwrp_override/providers/buck_passer.rb b/knife/spec/data/lwrp_override/providers/buck_passer.rb new file mode 100644 index 0000000000..2061b391dc --- /dev/null +++ b/knife/spec/data/lwrp_override/providers/buck_passer.rb @@ -0,0 +1,5 @@ +# Starting with Chef 12 reloading an LWRP shouldn't reload the file anymore + +action :buck_stops_here do + log "This should be overwritten by ../lwrp_override/buck_passer.rb" +end diff --git a/knife/spec/data/lwrp_override/resources/foo.rb b/knife/spec/data/lwrp_override/resources/foo.rb new file mode 100644 index 0000000000..1d6be84e58 --- /dev/null +++ b/knife/spec/data/lwrp_override/resources/foo.rb @@ -0,0 +1,11 @@ +# Starting with Chef 12 reloading an LWRP shouldn't reload the file anymore +unified_mode true + +actions :never_execute + +attribute :ever, :kind_of => String + +class ::Chef + def method_created_by_override_lwrp_foo + end +end diff --git a/knife/spec/data/mac_users/10.9.plist.xml b/knife/spec/data/mac_users/10.9.plist.xml new file mode 100644 index 0000000000..be3456ea6c --- /dev/null +++ b/knife/spec/data/mac_users/10.9.plist.xml @@ -0,0 +1,560 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>KerberosKeys</key> + <array> + <data> + MIIBVKEDAgEBoIIBSzCCAUcwc6ErMCmgAwIBEqEiBCAJxcIcjX3sMb98++d0 + YvKqc351+CJJTMpyJO5mwWFMCaJEMEKgAwIBA6E7BDlMS0RDOlNIQTEuNEEy + NDA4NDVBMjU0OUZCOEUwRjI4NEU1NkUyODE3NzU2RUU5Q0QyMnZhZ3JhbnQw + Y6EbMBmgAwIBEaESBBDzYvuM3CLsLOGCIX4FJ8vdokQwQqADAgEDoTsEOUxL + REM6U0hBMS40QTI0MDg0NUEyNTQ5RkI4RTBGMjg0RTU2RTI4MTc3NTZFRTlD + RDIydmFncmFudDBroSMwIaADAgEQoRoEGCkvuVvN92vqnm0cy+9GWNBoIEoW + XtUNx6JEMEKgAwIBA6E7BDlMS0RDOlNIQTEuNEEyNDA4NDVBMjU0OUZCOEUw + RjI4NEU1NkUyODE3NzU2RUU5Q0QyMnZhZ3JhbnQ= + </data> + </array> + <key>ShadowHashData</key> + <array> + <data> + YnBsaXN0MDDRAQJfEBRTQUxURUQtU0hBNTEyLVBCS0RGMtMDBAUGBwhXZW50 + cm9weVRzYWx0Wml0ZXJhdGlvbnNPEIASYBqQ2xfL+LpICOY4L7DTudimwaGQ + R3v2gKshr7YGVGcTblXMIIpvdBVuPa8g+xM2nvS3uvoEfYA1n7RqSKStzNVI + 67M4UbCTR8yoQ0Gn+TonFHND+J+4Q/tGwAF9Jkr6SXa6rPlBuRW9HsHKJMML + PnWeAkA+AvWf5/9ZOKdjbE8QIO6VS+Ry/cYN34lIR4FDOZNiXwBq9uyBDAj0 + mn5BOUahEYayCAsiKTE2QcTnAAAAAAAAAQEAAAAAAAAACQAAAAAAAAAAAAAA + AAAAAOo= + </data> + </array> + <key>_writers_hint</key> + <array> + <string>vagrant</string> + </array> + <key>_writers_jpegphoto</key> + <array> + <string>vagrant</string> + </array> + <key>_writers_passwd</key> + <array> + <string>vagrant</string> + </array> + <key>_writers_picture</key> + <array> + <string>vagrant</string> + </array> + <key>authentication_authority</key> + <array> + <string>;ShadowHash;HASHLIST:<SALTED-SHA512-PBKDF2></string> + <string>;Kerberosv5;;vagrant@LKDC:SHA1.4A240845A2549FB8E0F284E56E2817756EE9CD22;LKDC:SHA1.4A240845A2549FB8E0F284E56E2817756EE9CD22</string> + </array> + <key>generateduid</key> + <array> + <string>11112222-3333-4444-AAAA-BBBBCCCCDDDD</string> + </array> + <key>gid</key> + <array> + <string>80</string> + </array> + <key>home</key> + <array> + <string>/Users/vagrant</string> + </array> + <key>jpegphoto</key> + <array> + <data> + /9j/4AAQSkZJRgABAQAAAQABAAD/4ge4SUNDX1BST0ZJTEUAAQEAAAeoYXBw + bAIgAABtbnRyUkdCIFhZWiAH2QACABkACwAaAAthY3NwQVBQTAAAAABhcHBs + AAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkZXNjAAABCAAA + AG9kc2NtAAABeAAABWxjcHJ0AAAG5AAAADh3dHB0AAAHHAAAABRyWFlaAAAH + MAAAABRnWFlaAAAHRAAAABRiWFlaAAAHWAAAABRyVFJDAAAHbAAAAA5jaGFk + AAAHfAAAACxiVFJDAAAHbAAAAA5nVFJDAAAHbAAAAA5kZXNjAAAAAAAAABRH + ZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAUR2VuZXJpYyBSR0IgUHJv + ZmlsZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAbWx1YwAAAAAAAAAeAAAADHNrU0sAAAAoAAABeGhySFIAAAAo + AAABoGNhRVMAAAAkAAAByHB0QlIAAAAmAAAB7HVrVUEAAAAqAAACEmZyRlUA + AAAoAAACPHpoVFcAAAAWAAACZGl0SVQAAAAoAAACem5iTk8AAAAmAAAComtv + S1IAAAAWAAACyGNzQ1oAAAAiAAAC3mhlSUwAAAAeAAADAGRlREUAAAAsAAAD + Hmh1SFUAAAAoAAADSnN2U0UAAAAmAAAConpoQ04AAAAWAAADcmphSlAAAAAa + AAADiHJvUk8AAAAkAAADomVsR1IAAAAiAAADxnB0UE8AAAAmAAAD6G5sTkwA + AAAoAAAEDmVzRVMAAAAmAAAD6HRoVEgAAAAkAAAENnRyVFIAAAAiAAAEWmZp + RkkAAAAoAAAEfHBsUEwAAAAsAAAEpHJ1UlUAAAAiAAAE0GFyRUcAAAAmAAAE + 8mVuVVMAAAAmAAAFGGRhREsAAAAuAAAFPgBWAWEAZQBvAGIAZQBjAG4A/QAg + AFIARwBCACAAcAByAG8AZgBpAGwARwBlAG4AZQByAGkBDQBrAGkAIABSAEcA + QgAgAHAAcgBvAGYAaQBsAFAAZQByAGYAaQBsACAAUgBHAEIAIABnAGUAbgDo + AHIAaQBjAFAAZQByAGYAaQBsACAAUgBHAEIAIABHAGUAbgDpAHIAaQBjAG8E + FwQwBDMEMAQ7BEwEPQQ4BDkAIAQ/BEAEPgREBDAEOQQ7ACAAUgBHAEIAUABy + AG8AZgBpAGwAIABnAOkAbgDpAHIAaQBxAHUAZQAgAFIAVgBCkBp1KAAgAFIA + RwBCACCCcl9pY8+P8ABQAHIAbwBmAGkAbABvACAAUgBHAEIAIABnAGUAbgBl + AHIAaQBjAG8ARwBlAG4AZQByAGkAcwBrACAAUgBHAEIALQBwAHIAbwBmAGkA + bMd8vBgAIABSAEcAQgAg1QS4XNMMx3wATwBiAGUAYwBuAP0AIABSAEcAQgAg + AHAAcgBvAGYAaQBsBeQF6AXVBeQF2QXcACAAUgBHAEIAIAXbBdwF3AXZAEEA + bABsAGcAZQBtAGUAaQBuAGUAcwAgAFIARwBCAC0AUAByAG8AZgBpAGwAwQBs + AHQAYQBsAOEAbgBvAHMAIABSAEcAQgAgAHAAcgBvAGYAaQBsZm6QGgAgAFIA + RwBCACBjz4/wZYdO9k4AgiwAIABSAEcAQgAgMNcw7TDVMKEwpDDrAFAAcgBv + AGYAaQBsACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjA5MDtQO9A7kDugPMACAD + wAPBA78DxgOvA7sAIABSAEcAQgBQAGUAcgBmAGkAbAAgAFIARwBCACAAZwBl + AG4A6QByAGkAYwBvAEEAbABnAGUAbQBlAGUAbgAgAFIARwBCAC0AcAByAG8A + ZgBpAGUAbA5CDhsOIw5EDh8OJQ5MACAAUgBHAEIAIA4XDjEOSA4nDkQOGwBH + AGUAbgBlAGwAIABSAEcAQgAgAFAAcgBvAGYAaQBsAGkAWQBsAGUAaQBuAGUA + bgAgAFIARwBCAC0AcAByAG8AZgBpAGkAbABpAFUAbgBpAHcAZQByAHMAYQBs + AG4AeQAgAHAAcgBvAGYAaQBsACAAUgBHAEIEHgQxBEkEOAQ5ACAEPwRABD4E + RAQ4BDsETAAgAFIARwBCBkUGRAZBACAGKgY5BjEGSgZBACAAUgBHAEIAIAYn + BkQGOQYnBkUARwBlAG4AZQByAGkAYwAgAFIARwBCACAAUAByAG8AZgBpAGwA + ZQBHAGUAbgBlAHIAZQBsACAAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABz + AGV0ZXh0AAAAAENvcHlyaWdodCAyMDA3IEFwcGxlIEluYy4sIGFsbCByaWdo + dHMgcmVzZXJ2ZWQuAFhZWiAAAAAAAADzUgABAAAAARbPWFlaIAAAAAAAAHRN + AAA97gAAA9BYWVogAAAAAAAAWnUAAKxzAAAXNFhZWiAAAAAAAAAoGgAAFZ8A + ALg2Y3VydgAAAAAAAAABAc0AAHNmMzIAAAAAAAEMQgAABd7///MmAAAHkgAA + /ZH///ui///9owAAA9wAAMBs/9sAQwACAgICAgECAgICAgICAwMGBAMDAwMH + BQUEBggHCAgIBwgICQoNCwkJDAoICAsPCwwNDg4ODgkLEBEPDhENDg4O/9sA + QwECAgIDAwMGBAQGDgkICQ4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4O + Dg4ODg4ODg4ODg4ODg4ODg4ODg4O/8AAEQgBMQEuAwEiAAIRAQMRAf/EAB8A + AAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQE + AAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYX + GBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3 + eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfI + ycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEB + AQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMR + BAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJico + KSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWG + h4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW + 19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A/fyiis6/1Wz0 + 6P8AfyAyY+WJeWP4dvxoA0aw9R1+zsN0at9puR/Ah4H1NcjqHiG9vQ0cZ+yQ + H+FD8x+p/wAK5+gDWu9a1C8u1kad4gpyiRkqF/z71u6d4pI2xaiu4dPOQc/i + P8K43v0P5UlMZ7LDPDcW6ywSJLGejKcipa8gtL65sLjzLaV4z3HVT9RXb6d4 + mt7grFegW0398fcP+H40hHUUUgIZQykEEZBHeloAKKKKACiiigAooooAKKKK + ACiiigAooooAKKKKACiiigAooooAKKKguLmC1tzLcSpDGO7H/OaAJ6K4y48W + gXyi2tvMtwfmLnDN9PSuisNVs9RjzBJiQfeibhh+Hf8ACgC3cgHT5wQCDG2Q + fpX4u/CD9rf4h/C+aHRtXkfxt4PjbaLG/mP2i2XP/LGc5YADorbl7AL1r9o7 + j/jxn/65n+VfzYS/8fMn+8f51+qeG+W4XHUcXSxEFKPub/8Ab2z3T9D8p8S8 + zxWBq4Srh5uMvf2/7d3WzXkz99/hb8cPh38XtCW48J6yn9ppGGutHvMRXtv6 + 5TJ3L/tIWX3zxXrtfgD8JPhh8TviF4ut9Q+Hlvd6ZDZTgv4nluHtLSxYd1mX + 5nkGfuRbm+lftD4C1HXfDvw+0vRfGHiW48batBFtudbeyS2aZvaNf4QOAxO4 + 9W5r57jHh7BZXX5cPXUr/Z3lH1a0++z8nufQ8G8R43NaHNiKDjb7X2Zeiev3 + XXpseu0VBb3MF1biW3lSaM91NT18Yfankuk/FDQfHPgxNb8B6zYa1ojna17a + ybnjb+5Ihw0T/wCy4Bqkzs8hd2Z3JyWJyTX4a+HPF3ijwL4/bxB4O13UPDus + K5DTWr/LMufuSocrKn+y4Ir76+Fv7YXhzXfs+j/FO1tvB+sHCLrtorNplwfW + VOXtifX5o/da/Rc/8O8bg71cL+9h2+0vl19Vr5I/OOHvEbBYxqli/wB1Pv8A + Zfz6fPTzPs6jt61HFLDPY293bTwXVpcIJLe4t5VlimQ9GR1JVgfUE0/mvzux + +jppq6F70lL6elHagBPyo7elHejvQM1tP1m905wI3MkPeJ+V/D0ru9O1yy1D + CK3k3B/5ZOeT9D3ry/tR0YH360CPaaK8607xJd2m2K6zdQDjJPzj6Hv+NdzZ + 39rfweZbSq/95Twy/UUgLlFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABS + MyohZ2CqBkknAFYmo69Z2AZFb7Rcj/lmh6fU9q4S/wBWvNRc+dJiLPyxJwo/ + x/GgDrNR8TwQho7BRcydPMP3B/jXE3V3c3lwZrmV5X9+g+g7VXzj2PrS8EZP + FMBvtT1ZkkDozI68gqcEU0gjP86Tv9aBnWWPiWXyWttQ/eIyFRMo+YccZHQ/ + pXwD8MP2PdC0iSDWvivd2/inVc+Yvh+xkZdOgPXE0gw9wR3Vdqf71fadHQ5r + 1MvzvG4GlUp4efKp2u1vpfZ7rfpqeRmGR4LHVadTEQ5nC9k9tbbrrt10IoIY + LTTbaxs7e2srC2Ty7a1toVihgX+6iKAqj2AqXtSVla7r2ieFvDjav4j1S00f + TRwstw3Mp/uxqPmkb2UH8K86EJ1JqMVeT6LVtnpznClByk0orvokv0N62u7i + yuRLbTPE4646H6jvV3U/i74Q8M3MVh4p1SGw1Z03rZwRvNMU/vtGgLIuSOWx + nPFfEXjj9ojWNT87TvAcE/hzTzlW1S4UG+lHqi8rAD6/M/uK8d8JPJL4xvbi + eSWe5lgZ5ZpXLySMWXJZjyT7k1+g5XwBXnTdbGPkX8q+L5vZfi/Q/PM08QKE + aipYKPO/5n8PyWjfrovU+P5/+P2b/fP86irqfG/hfUvBXxe8SeE9XXGoaVqE + ttK23Ak2sdrj/ZZcMPYiuWr97pVI1IRlF3TV0fz/AFacqc5QkrNOzPpr4GfE + bxb4B8GyDQdQEmlm/czaReZks5OFzhc5jb/aQg/WvvvwJ8ZvCPjeSGwkk/4R + rxG/A02/lGyZv+mE3Cv/ALp2t7GvzM8Af8iHL/1+yf8AoK128dvJeXcFlBbT + XtzO4WG2hiMkkrdgqgEk/SviOIuE8BmUpTkuSf8AMv1Wz+evmj7vhzizH5bC + MIPmh/K/0e6+Wnkz9V2VkdlYMrDhlIwRTfzr5Ut/E3xQ+CX7Nlz4x8foniHQ + bW7tbW38O3FyDqduk0mzd9p5CbRyIn389Ste2fDz4peBPipob3ngvW0vbmJN + 15pNyvk6hZ/9dISclf8AbQsnvX4hj8jr4eMqsGqlJO3PHWN9N+268uibP3DA + Z/h8TONKadOq1fklpK3l32fn3SO/780d6Xr3/Wkrxj3Be1JS/likoAU9KfFL + LBOssMjxSA5DKcGmUn50Adrp3in7sOornt5yD+Y/w/KuximingWWGRJY26Mp + yDXjXOenWuL8V/Fzw58MWYahqMlzrRXcmiWJElxJ6GQH5Yl/2nwfQGt8LhK2 + JqKnRi5SfRf1+OxzYrF0cNSdWtNRiur/AK/A+naK5vwdr0nij4VeHfEktqtl + JqenxXTW6ybxEXUNt3YGcZxnFdJWVWnKnNwlutDWlUjUgpx2auvmFFFFQWFF + FFABRRRQAUUV4D8evjfbfBHQ/Cmpajo17qmk6tqD2l3NYyL9otFEe4SJG3yy + e67lPoc104PB1sVWVGjHmk9l30ucuMxlHCUXWrStFbv52PcL3UbTT4d1zKFY + jKoOWb6CuG1LxHd3ZaODNrbnsp+Zvqf6CvOvCnjfwv8AETwqfEfhHxDZ+JNP + JHnyRMfOt2P8E8bfPE3swx6E10fFZVaU6U3CpFqS3T0a9UbUa1OtBVKclKL2 + a1T+Yd6KPyrk/F3jjwv4F0lbnxLqaWs0i7rawhXzbu5/3Igc4/2mwvvVUKFW + tUVOlFyk9ktWLEYilQpupVkoxW7bsjq6O9fK+hftaeBrj4n3nhvxlpl34It9 + yHT9Wkm+02xVlyFudq5ibP8AGoZPp1r6lhlgudOt7y0uLe8sbhBJbXVtKssM + yf3kdSVYe4NduZZPjcvmo4mm4327P0aun566HDlmdYHMIuWGqKVt+69U7P8A + Akyce1LwTx1pM0qqzyBEVmduiqMk15h6YnQ1HPNBa6bc315cW1lY2yeZc3Vz + MsUMCf3ndiFUe5NeXeOvjH4R8DtLY+b/AMJJ4hQY/s2wlG2E/wDTaYZVP90Z + b2Ffm58ZfGXxR+IOpvdeKdU/tDwvFIXtNJ0pDFY2Y7F4cku4/wCejlifUV9n + w9wTjczanP8Ad0+73f8AhXX1dl2bPiuIuOMFlicIfvKnZbL1fT0V33sfUfxR + /bE0LR/tOjfCmzt/FWqDKN4gv42GnQHpmGM4a4I7M21P96vmXR/FPiTxra3f + iLxdrmoeItblvJAbq8kzsXC4SNRhY0HZVAFfP4IIyCCD0INexeAf+RCk/wCv + 2T+S1+1Zbw1gMqo2w8Pe6yesn8+i8lZeR+JZjxNmGbV74ifu9IrSK+X6u78z + ta6zwd/yM0//AF6t/wChLXJnpXt3wO+HWp/EHx1rENldR2FtZWAae5kjLKGe + RQicdyFcj/dNGZ4inQws6lR2iupeWYepXxUKdNXk+g79vb4c/wBm/Erw78Tr + CDbaaxENP1RlXgXMS5iYn1eIFfpBX58V+/nx0+HafFH9lzxX4TWJZNTktTca + UxxlbqL54sHtuI2E/wB12r8BXR45njkVkkUkMrDBBHUEV4/h1m/1vLPYyfvU + tPl9n9V8j1vEfJ/qmae2ivdq6/P7X6P5n1L+zt8Orj4lWd/pltr+j6StndNL + eRvKJL7yyF+aK3zlx1+c4UY5r9FfBnw/8KeArBk8OacFvZE2z6pdESXk/wBX + x8i/7KAD61+JVpd3mna1aalpt5eabqdrIJLW8tJmimgYfxI6kFT9DX218Lv2 + x9V082+jfFuym16yGEXxJpsCi9jHTNxAMLOPV02v3IauDjnh/OcYnPDVOan1 + gtH/APbej26Js7+A+IMmwbUMTT5anSb1X/2vqvm0j6A/a0/5MP8AEIH/AEGN + N/8AR5r8p7G+vtK16z1XSr+90rVbSQSWt7ZTtDPA3qjqQR/XvX6f/tJ+I/D3 + i3/gnHrOv+Fdc0zxFos+sad5d5Yzb1z5/KsPvIwyMqwBHpX5b11eGlJxympC + as+eSaf+GOjX6HL4l1oyzenUpyuuSLTT85apo+5/hd+2TqFobfR/i9Yy6va8 + KnibS7cC6QetxbjCy+7x7W7lWr700TW9F8TeErTX/Der6dr+h3IHkX1jMJIm + OM7T3Vh3VgGHpX4SV2Hgjx/4y+HHis6z4L1670S7fi5iXD212v8Admhb5JB9 + Rn0IrHiDw4wmKvVwbVOfb7L+X2flp5G3DviTi8JaljF7SHf7S+f2vnr5n7fY + 70vavkv4XftbeDfF722j+PobbwB4kchFvDIW0q7Y8cOfmtyf7r5XPRhX1hcT + QWelSajeXVraaakQle8mmVYAhHD+ZnaVPYg89s1+MZnk+My+t7LE03F9Oz9H + s/kfteV51gsxo+1w9RSS37r1XQf+VYviHxLoHhLw7/aviXVrXSLM5ERlOZJz + /djjHzOfoMepFfP3jj9ouxtPO074fW0eqXPKnWr6I/Zk94Yjgyf7z4X2NfKm + r6vq3iDxFNq+u6le6xqkv37m7k3vj+6Oyr/sqAPavrcj4BxWKtUxX7uHb7T+ + XT56+R8jnnH+Fw16eEXtJ9/sr5/a+Wnme7eOP2hde1kzad4Khn8LaU2Va/kw + dQnHqpGVgB/2ct/tCvng5aaSRmeSWRi8kkjFndj1LMeSfc0UV+t5blOEy+n7 + PDw5V17v1e7/AE6H5JmWbYvH1faYibk+nZei2X9XP1/+E3/JsXgD/sAWv/op + a9Crz34S/wDJsPgD/sAWv/opa9Cr+ccx/wB7q/4n+Z/R+W/7pS/wr8kFFFFc + R2hRRRQAUUUUAFfn7/wUE/5Ij4B/7Dkv/og1+gVfn7/wUE/5Ij4B/wCw5L/6 + INfVcEf8jzD+r/JnynHH/IixHovzR+YfhzxJ4h8H+MbfxD4U1vUvDutw8JeW + MuxyP7rjo6HurAg+lffXws/bF0rVWtdD+LFjFoOpMRHH4i0yBmspm6Dz4Blo + ST/Em5OfurX511Ys/wDkNWP/AF8x/wDoYr98zvhvAZrC2Ih7y2ktJL5/o7ry + P5+yPiTMMqqXw89OsXrF/L9VZ+Z+mPjr9oq7lln0z4fWr2EQJR9bv4QZ294Y + TkJ7M+W7gCvmO6ubq/1a41C/urm/1Cdt091cymSWU+rMeT/Smy/8fMn+8f51 + H+PNcGVZLg8up8mHhbu+r9X+m3ZHqZrnWMzGpz4id+y6L0X6792eJ+N/+SmX + n/XCH/0Cul+Gnxj8ffCfUifCmrB9Hkk33WhX4M1hcep2ZzG3+3GVP1rmvG// + ACUy8/64Q/8AoFcpX0VXCUcTh/ZVoKUWtU1dHzFLF1sNiPa0ZuMk9GnZn6se + B/2q/hr4s8I3Fxq0eqeFfEdrDvn0UxG5+084JtpVADrnGQ+1lByc9a8s8d/H + bxT4shuNO0USeEvD7gq0NtNm7uF/6azDoD/dTA9zXxj4A/5Hub/ryk/mtewZ + z/8AWr43DcFZVgsS6sIXe6UndR9P83d+Z9riONs2x2FVKpUstnZWcvX/ACVl + 3QgAVcAADPQU4Eq2VJU+opMcUV9KfOHJa14O0vVWknt9ulX7cmSJP3ch/wBt + P6rg/WrHhTS7zR/DM1jfJGswu3ZTG+5XUhcMD+B6810tFW6knHlZkqUVLmW4 + vb0r9RP2cPBX/CI/s3afd3MIj1XXD/aFwSOQjDEK/TZhsdi7V+fXwu8HP48+ + Ovh7w3tY2c1wJL5hn5bdPnk57EqCoPqwr9f0RIoUjjRY40UKiKMBQOgA7Cvy + zxHzTlp08JF7+8/Tp+N/uP1Xw3yvmqVMZJbe6vV7/hp8x1fiL+1z8Of+Ff8A + 7YmtXFpB5WieIh/a1jtHyq0jHz09OJQ5wOiulft1Xx7+2r8OP+Ez/ZQk8S2U + Hm6z4TmN8hVcs1q+FuF+gASQn0iNfL8B5v8AUc1gpP3anuv57P7/AMGz6jj7 + J/r2UzcV71P3l8t1934pH400UUV/SB/NRPDdXVtZXtrb3VzBaXmz7Zbxyssd + zsO5PMUHDFTyCRkHpUPB6cH0pKKVkF29wopc8YPIpKYAQCCCAQRgg969m8Ba + tq138N7jRLvVtTutFsb4NZafNdO9vblkydiE4XJ54HHbFeM16v8ADv8A5F3V + /wDr8T/0CufFRThdrY6sJKSqaPc9AzRTXdY03SMqr6k/kK9Av/hh400j9nPx + H8Utb0ibRvDOk2iXEcV4DHd3wZ0QeXGeVX5wdz4z2B615FfE0qPL7SSXM0lf + q3sl3PZo4atW5uSLfKm3bolq2+xwPOKWs/TdV0/V7Iz6ddJcIo/eJjbJH/vK + eR9envV/vW7TWjME76o/UT9m7xcnij9mTS7OR1/tDQ2OnTqODsUAxNj02FVz + 3KtXvtfmv+yz4y/4R/8AaAfw/cS7NP8AEFv5GCcKLiPLxH8R5ifVxX6UV/Pf + GOWfU80qJL3Ze8vnv+Nz+heDsz+u5XTbfvR91/Lb8LBRRRXy59SFFFFABRRR + QAV+Vv7fPj9NS+KHhf4c2bo8Wj2xv9QK8kTzDEaH0Kxru9xKPSv1D1TU7LRf + DOo6xqU62unWNrJc3UzdI441Lux9gATX883xA8YXvj/42eJ/GWob1uNX1CS5 + EbHPlITiOPPoiBVHsor9K8M8q9vmEsTJaU1p/ien5X/A/M/E/NvYZfHCxetR + 6/4Vr+dvxOPqzZ/8hqx/6+Y//QxVapYJBDfwTEFhHKrlQcZwQcfpX70z8BTP + paX/AI+JiSFVSSzMcADPUnsK4HWfHVjZl4NIVNTuhwZmyIEPt3c/TA964TXf + EOq69LIbiZUsN2VtYMrGv+8OrH3PFc7XHSwyteR21cW3pEtXt7d6jqct7fTG + 4upMbnIA4HAAA6AdhVWiiutHG3c7fwB/yPM3/Xk/81r2DvXj/gD/AJHqb/ry + f+a17BXn4n+IelhP4YdqPzoorA6gooxV/StNvNa8Tado+nxGe/vrlLe3jz95 + 3YKo/M0pSUU29kOMXJqK3Puj9kbwV9k8J6347u4cT37/AGHT2Yc+ShBkYezO + FX6xGvsquf8ACnh2z8JfDbRPDVgB9l060SBWC48wgfM5HqzZY+5NdBX81Z7m + Tx+OqV+jenotF+B/SuRZasBgadDqlr6vV/iFVb6ytNS0W806/gjurG6geC4g + kGVkjdSrKR6EEirVFeUm07o9ZpNWZ/PN8U/A118Nv2g/Ffgq68xhpl8yW0j9 + ZYGw8Mh92jZCfQkiuAr9Lv2+vhxlPC3xTsIOR/xKNXKj/ekt3OP+2qlj/wBM + x6V+aNf1LwzmyzHLaVe+trP1Wj/z+Z/KvE+UvLcyq0Oid1/heq+7b1QUUUV7 + x4AUUUUAFfSn7PXw18V/E2fXdM8MQ2IW3u4mvry8uAkVqrIcMVB3ueOig+5F + fNdd18OdfvvD3xQtLnTtQvNKvJh5cF3aTGKWKQcoQw9TkEHg55BrjzCFaeHm + qMkpdG1dfddf132O3LqlGGJg6ybjfVJ2f32f9dtz9oPhn+zv4I+H0ttql3Gf + FXimPkalfxjZA3/TCLlY+3zctx96qH7XH/KOb4pf9g6L/wBKYa8Z+G37WV7Z + m30j4nWbX1uMIuvafBiRfeeAdf8Aej9PuV6f+05r2i+Jv+CYPxK1nw/qljrG + lz6ZEYrm0mEiH/SITjI6EZ5B5HevwSrgM1o55hp468r1I2lvH4lt0Xpp6H9A + UsflNbI8TDA2janO8dpfC9+r9bv1PxQgnntb1Lm1nltrlD8ksTbWX8f6V6Ro + /j4ErBr0WD0F7AnH/A0H81/KvMu9B6Gv6EnTjPc/nanVlB3R9Q6Tqk2n6xpm + t6VcgT280d1Z3EZyNysGRh+IBr9lvCfiG08WfDXQ/Ellj7PqNmk4UHPlsR8y + H3VsqfcV+Jehf8iLov8A15J/Kv0U/ZH8Y/bvh9rXgm6lzcaXN9rslY8mCU/O + oHosnJ/661+U+ImWe2wUcRFa03r6PT87fifrPhzmnscY8PJ6VFp6rX8r/gfY + NFFFfih+2hRRRQAUUUUAfHP7bPxC/wCER/ZNPhmzn8rVvFdz9jUKcMLWPDzs + PY/u4z7Smvxtr6r/AGxfiF/wnH7Y+q6daT+bo/hmMaVbBW+UyqSbhsevmEp7 + iNa+VK/pXgfKfqOU00170/efz2+5W+Z/MvHWbfX83qNP3Ye6vlv+N/kFFFFf + Xnx4qkq4ZSQw6EU5ni2M0pWHAyX6KPc+n1Fdx4A+Gfjj4oeJH03wVoU2prEw + F5qEreTY2XvLO3yqf9kZY9hX6I/Cz9lPwL4Ee11jxa0HxC8WR4dGurfbplm/ + X91bt/rGB/jlz7IK+Yz7i3L8qTjVlzT/AJVq/n2Xr8kz6jh/hHMc2d6UbQ/m + e3y7v0+dj8vLuyvrAWRv7K9sVvIBPZNcW7xi6iPSSMsBvXjquRVav3R8WeFv + DXjzwlJoHjPQ7DxJpDcrBdpzAf70LjDRMOxQj8RxX5//ABR/Y88Q6J9o1j4V + 3dz4v0kZZtCvGVdTgHUiJ+EuQPT5ZPZq8PIvEbAY2Xs8SvZSe13eL/7e0s/V + JefQ93PvDjH4GPtMO/ax62VpL/t3W/ybfkfMvgD/AJHqb/ryf+a17Aa8i8Cx + TW3xKvrS5hntLy3tZI7i3njaOWFgVyrowBU+xFeu19liPjPjsIrQDsaKD60d + qwOgK+qf2UvBX9t/Ge88WXcW6x0GD9wSOGuZQVX67U3n2JU18rd6/WL4GeCv + +EH/AGcNDsJ4fJ1S9X7fqIIwwllAIU+6oEQ+6mvjeOc0+qZbKEX71T3V6dfw + 0+Z9nwNlf1vMozkvdp+8/Xp+OvyPX6KKK/BT97CiiigDg/if4Gs/iV8A/FPg + i9aONdUsWjgmdciCcYeGTHfbIqNjvjFfhh8Svg/8QPhN4jFh4z0Kezhkcra6 + jD+8s7rv+7lAwTjnacMO4Ff0F1maxoukeIfDd1o+u6ZY6xpVymy4tLyBZYpB + 6FWBFfY8K8YV8mbhy81OTu1s790/6+R8ZxXwbQzlKfNy1Iqye6a7Nf1bzP5u + qK/QP9qT9lbwp8PPhzqHxJ8D6jPpmlRXMcd1oVzmVVMsgQGGQncACfuvnvhu + gr8/K/fsmznDZnhlXoPTbXRp9v8Ahj+fs6yXE5XiXh8Qtd9HdNd/w66hRRRX + qnkhSqzpIskTFJUYMjDswOQfzpKKAPovTr9NU0Cy1GPgXEQdh/dbow/76BrQ + M9z/AMItrehpeX1vo+sweRqtpBOUjukDBhuXpuBAIbGRjrivM/h7qO63v9Hk + blD9ptwfQ4Dj88H8TXpFeTVppSaa/rdHtUKjlFST/rZnj2s+CNRsA9xppfVb + MclQuJ4x7qPvD3X8q4jOQfbg+1fTIODkZFc7rXhjStbDSTRm0vyOLuAAMf8A + fHR/x5966aeJe0jkq4PrEu6F/wAiLov/AF5R/wAq9n+DHjL/AIQf9ovw9rE0 + vladLN9j1DJwvky/KzH2U7X/AOACvIdPtmsvD9hZSOsr28CxF1GA2O4Bq53r + zcZhoYmjOlPaSa+89TA4mphqsKsN4tP7j9wKK8l+B/jH/hNv2bvD+pTS+bqV + rH9hvyTlvNiAXcfdl2P/AMCr1qv5jxmFnhq86M94tr7j+nsHioYmhCtDaST+ + 8KKKK5jpCvPPix45g+G37Oni3xpMY/M02wZrRH6SXDYSFD7GRkB9ia9Dr82/ + 2/PiF5en+EvhhZT/ADSsdX1VVPO0bo4FPsT5zEH+6hr3eGsq/tHMqVBrRu79 + Fq/8jweJs2WXZZVxF9UrL1ei/wAz80rm5nvNRuLu6mkuLqeRpJpXOWdmOSxP + ckkmoKKK/qZK2h/KbberCj8jz0PIPsfUe1FFMD9DPg5+1n4Qj8NaX4N8e6Fp + Pw+itVEVnqWh2nl6Qe2ZYFy1ux7uNynvivt6Ce3u9Mtr6yuba+sLmPzLW6tZ + llhnX+8jqSrD3Br8Fa9M+Gvxf8ffCfVTJ4Q1cDSZJN93oV8pm0+59SY8/u2/ + 24yrfWvy3iLw2o4hyrYGXLJ6uL+F+j3T+9eh+qcN+JVbDKNHGx54LRSW6Xps + 19z9T9ozXJ+LvG/hbwNpK3XifVFtJJF3W1jCvm3dz/uRDnH+02F96+Urr9qb + WvFHw4sZ/Cfh2PwlfzqyXt3c3Au2hkU4YWwwAF7h3BYenevBrq5ur/VrjUL+ + 6utQ1C4bdPdXUpkllPqzHk/yFfLZN4d4mpLmxr5Euis5P56pL735Lc+szjxG + w1OHLgVzt9Wmkvlo2/uXqeg/E/4gxfEnxxbawfC2j6LJao0UF75YfUp4zgbZ + 5xjevAwmCF7GvOPeijvX6zg8HRwlGNGirRjstf1PyXGYytiq0q1V3lLd7fkF + FBo4rqOY9a+CHgr/AITr9ozQ9Mni87S7R/t2ogjKmGIg7T7MxRP+BV+s9fKX + 7KHgr+xvg9feL7qLbfa5Pttyw5W2iJUY9Nz7z7hVNfVtfg3HOafW8ycIv3af + u/Pr+OnyP3rgXK/qmWqpJe9U975dPw1+YUUUV8YfZhRRRQAUUUUAfKX7af8A + yYH4j/7CFl/6UJX4qV+1f7af/JgfiP8A7CFl/wClCV+Klfv3hh/yKJf43+UT + +ffFL/kbx/wL85BRRRX6Mfm4UUUUAaWj6idI8U2OojJSKT96B/FGeGH5HP4V + 9DHGcq25DyrDowPQ/lXzP1HPIr27wbqX9o+BYI5G3XFk32eTPUgcofxXj8K5 + MVDRSO3Bz1cTqaDRRXEegA6UUfrQaAPrn9knxl/ZvxQ1fwZdTYttXt/tFmrH + pPECSAP9qPcT/wBcxX6EV+LXhnXrzwv8QdF8R2B/0rTryO4QZwH2tkqfYjIP + sTX7K6TqdnrfhfTtY0+TzrC+to7i3f8AvI6hlP5EV+LeImWexxkcTFaVFr6r + /gW/E/afDrM/bYKWGk9YPT0f/Bv+BoUUUV+eH6IFfhh+1hd3N1+3/wDEP7TP + JP5NzBFFuP3EW2iwo9AP6n1r9z6/Cn9qj/k//wCJP/X9D/6TQ1+meFiX9p1f + 8D/9KifmHiq2sspf41/6TI+faKKK/eD8DCiiigYUV3Ph7whHrXhCe9nuZrOd + 5ito4XcuF4YsvcE8ZB7VzuraFqeiThb+3xCxxHcxndE/0bsfY4NQqkW7X1NH + SkoqVtD1XwR/yTS0/wCvib/0KurrlfBH/JNLTr/x8Tf+hV1Wa82p8bPVo/Av + QKKKKgsO9b3hjw/eeK/iHo3hzTwfteo3aQIduQgY8ufZRlj7A1g9q+xv2R/B + X23xrrPjq7izBpsf2OwYjgzyDMjD3WMgfSWvJzzMlgMDUrvdLT1ei/E9bI8t + ePx1Ogtm9fRav8D7q0jSrPQ/Cmm6Lp0fk2Fjapb26eiIoUZ98DrWjRRX81yk + 5NtvVn9LRiopJLRBRRRUlBRRRQAUUUUAfKf7aKO37AXiUqjMFvrIsQM7R9oQ + ZPoMkD8a/FOv6MvGfhbTvG/wp8Q+EdWXOn6tYSWsrbcmPcpAcf7SnDD3Ar+e + XxDoWo+F/Hms+G9Xi8jVNLvZbS6TsJI3Ktj1GRwe4r9x8LMdTlg6uG+1GXN8 + mkv0/I/CvFXA1I42lifsyjy/NNv8b/mY9FISFUsegGTV+/02/wBMliS/tpLc + SoHhc8pICMgqw4PB6da/Urn5VZlGjtRR2pgFdj4H1H7F42W0dsQX6eScngOO + UP55H41x1KrOkiyRMUlRgyMOzA5B/OpnHmi0VCXLJM+lqOtU9Ov01TQLPUY+ + BcRB2Ufwt0YfgQaufyryWrHtJpq6DvR2oooGH41+kP7KvjH+3fgRceGrmXdf + aBc+WgJ5NvKS8Z/BvMX2AWvzer3P9nfxj/wiP7TOkJPL5em6wP7NusngGQjy + 2/CQIM9gWr5ji/LPruWVIpe9H3l8v81dH0/CGZ/Uszpyb92Xuv5/5OzP1Ooo + or+ej+hgr8Kf2qP+T/8A4k/9f0P/AKTQ1+4Wta5o/hzw1dazr+qWGjaVbJvn + u7ydYo4x7sTj8O9fg58f/FOheNf2wvHPifw1eNqGh314htLkxNH5oWGNCwVg + CBuU4yASMHAr9R8LKNT6/Vqcr5eW1+l7rS/c/K/FatT+oUqfMubnvbraz1t2 + PHqKKK/cz8JYU5I3lnjhjx5kjhEz0yTgfzptWbL/AJDlj/19R/8AoYoGj6Gt + LOPTtJtdPhGIraIRD3x1P4nJ/Gp2VZIHjkRJYnGHjdQysPcHrUsv/H1J/vn+ + dR9ulePe57dkirZWNppuni0sYRb2wkZ1jBJCljkgZ7Z7dqtUUAEsAAST0Aob + BK2gUfyqkupWL682lx3Mct+sZkeKM7vLAx94jgHnp1q7RYE09hyqzyqkas7s + cKqjJJPYV+vfwp8Gr4D+A3h/w6yKt9Hbia/I/iuJPmk574J2g+iivz6/Z08F + /wDCYftJ6ZPcReZpWij+0LrI+VmQjyl/GQqcdwrV+pNfkniPmnNUp4OL295+ + vT8Lv5o/XPDfK7U6mMkt/dXp1/Gy+TCiiivy4/UgooooAKKKKACiiigAr8kv + 27Phz/YHx90r4gWMGzTvEtt5V4VXhbuBQpJ7DfH5ZHqUc1+tteFftIfDj/hZ + 37I3ifQ7aDz9as4v7S0gBcsbiEFgi+7oXj/4HX1HB+b/ANnZpTqN2jL3Zej/ + AMnZ/I+W4yyf+0cqqU0ryj70fVf5q6+Z+DMn/HvJ/umvo6OGG58NWttdQRXN + s9rFvilXcrfIO39etfOMn/HvJ/umvpWwjlntdKtbeGa5upoIkhghjLySsUXh + VGST9K/pHFuyTP5rwSu2jznWfAJ+e40GTPc2U78/8Ac/yb8682mjkt7+S0uY + 3t7uP/WQyLtdfqP61+lHgf8AZ11XURFqXj65m0GxOGXSLVwb2Uekj8rCPYbn + +le8eIPg38LfE/w4h8Kar4L0oaTbg/Y5bRfJvLRj1kjuBmTeep3Fge4NfDY3 + xFy/CVlSV6ndxtZfPaXy08+h91gvDjMcXRdXSn2Ur3fy6fPXy6n4uUV9V/FP + 9k7xt4KS61nwVJcfELwtGC7xwwhdUs0HUyQLxKo/vxZPHKivlJWVgdpzglWH + QqR1BHYj0PNfZ5Zm2EzCl7XDVFJfivVbr5nxOZ5Ti8ureyxNNxf4P0ezXoep + fD3Ud1tf6PI3KH7Tbj2PDgfjg/ia9Hr560bUW0nxTY6iM7IpP3o/vRnhx+R/ + SvoY4yNrBlIyrDuD0P5U8TC0r9ysJO8LdhO/vR60Ud65zqD605HaOdZEdkdS + GVlOCpHQg+tNo7UAfsL8MvFyeOfgX4c8S71a5ubULeAfwzp8kox2G5SR7EV5 + J+0T+0ZpnwM0PTLODST4g8WarFJJZWjS+XDAikL5sxHzYJOFVR821uVxmvL/ + ANkLxjsvvEfgS6l+WRRqNgpP8Q2pMo+o8sgf7LGvgb9pT4hf8LJ/bC8V6zbz + +fo9lN/ZmlMGypggJXcp/uu/mSD/AH6/Isp4Np1eIKtCrG9KHveqfwr+uzP1 + zN+M6lHh+lXpStVn7vo18T/rujkviT8XvH/xZ8SjUfGmuz30UblrXT4v3dpa + 9v3cQ4BxxuOWPcmvNKKK/b8Ph6VCmqdKKjFbJaI/DMRiateo6lWTlJ7tu7Ci + iitjEKs2X/Icsf8Ar5j/APQxVarNl/yHLH/r6j/9DFDBbn0lL/x9Sf75/nUf + 50+X/j5k/wB8/wA6ZXjo90zdT1jTNItRLqF0kRI+SJfmlf6KOfxOB715ZrXj + XUtSV7exDaVYtwQjZmkH+0/b6L+del6v4f0rW483sBS5C4S7h+WVB6Z/iHsf + 0rynWvCWqaMHnCjUNPH/AC8wKcoP9teq/Xke9dWHVPrucWJdXpsaHw+AHjmY + AAD7E5/8eWvYO9eP/D8g+OJiCCPsL4IPutfSHgDwpceOPjJ4f8LwbwL66VZ3 + XrHCvzSv+CBj9cVhj60KSlUm7JK79Eb5bRnV5acFdt2Xqz9Af2YfBX/CM/s9 + x63cw+XqfiGQXbEjDCBcrCv0ILOP+ulfSFQWttb2Wm29naRJb2sESxQxIMKi + KMKo9gABU9fy9meOnjcXUry3k7/5L5LQ/qTLMBDBYSnQjtFW+fV/N6hRRRXC + d4UUUUAFFFFABRRRQAUUUUAfhP8AtQfDf/hW/wC154n0u1g+z6Jqrf2ppW1c + KsM5YsgHYJIJEA9FHrX0L+zp8cPg/pumWOgazpVv8PvGjRJbtr99OZ7bUSAF + AFw3Nrn/AJ5sAn+0a91/bm+HP/CTfs42Pjmxg36p4Xuc3BUcvZzFUfp12uI2 + 9l3n1r8hCAVIIBBGCCODX9BZTTo8S5DCnXm04+62nbVd+jurOzXpZ6n885vU + rcNZ/OpQgmn7yur6Pt1VndXT/wAj98dpAQ8FXQPGykFXU9GUjgg9iODTec1+ + Pfws+PvxC+FDQ2GlXqa/4TD5k8O6s7PbqO5gcfPbt/ufL6qa+w9T/bR+HkPw + wh1PR/DniW/8WzAr/YF2FhitXA+9LdLlXjz08tdzei1+b5p4fZtha6hSh7WL + 2a/9uT+H1vbzP0rKvEXKcVQc60vZSS1T1/8AAWt/S1/I+vLi6t9P0u51O9vL + XTbC0Xzbi9uZ1hht1H8TyMQFHuT9K/MX9pX4lfBfx3r7f8IP4X/tPxYsg+1+ + NbcmyguADynlYzd57SuFx1BavGfiR8W/HvxX1dZvGGsmXTYpC9nolmph0+1/ + 3Ys/O3+25Zj6ivNq/QeE+Anl1WOJxFRuoukW0l5N6OXpovJn53xbx9/aVN4a + hTSp95JNv06R9dX5oOo55Fe3eDdS/tDwLBG7brmzb7PKSeSByh/FePwrxGux + 8Daj9i8araO2IL9PJOTwHHKH88j8a/Q8RDmh6H55hp8tT1PZqKKO1eaeqFFH + tRQMv2HivWvBMl54m8PXItNXs7KcQSldwXfG0Z4+jHHocGvlkdBX0Hr3/Ii6 + 1/15P/KvnwdK6sHTinKaWrsr+S2/N/ecONqTajBvRXdvN2v+S+4KKKK7jgCi + ijnsGY9gqkk/gKACrVgM6/p49bqL/wBDFVFZXjV0ZWVhkMDkEVd07/kYtO/6 + +4v/AEMUnsNbn0fL/wAfMh/2j/Oo6fJ/x8yH/aNMrx0e4B9e9KCQcg4PsaTt + RTEZcOi6bbeJH1W1tltbt42jlEXyxuCQclegbjqK+9/2RPBWItf8fXkXLf8A + Eu04sO3DzMP/ACGoI9HFfEtnaXN/q1rY2cL3F5czLDBEg5d2IVVHuSQK/Yvw + J4VtvBPwh0DwvbbCLC0VJXXpJKfmkf8A4E5Y/jXwfiDmroYBUE/eqaf9urf9 + F95974e5Sq+Pddr3aev/AG89v1f3HW0UUV+Hn7iFFFFABRRRQAUUUUAFFFFA + BRRRQBl63o+n+IfBuraBq0AudL1KzktLuI/xxyIUYfkTX89HjzwjqHgL4yeJ + fBuqBjeaTfyWxcrjzVB+SQD0dCrj2YV/RXX5cft8fDj7D438NfFCwt9tvqUf + 9masyrx58aloXPqWjDr9IRX6V4aZv9Xx8sNJ+7UWn+Jbfer/AIH5n4nZP9Yw + EcVFe9Tev+F7/c7fifndRRRX70fgNgooooAKVWdJFkjYpKjBkb0YHIP50lFA + H0Xp1+mq6BZ6lHwLiIOw/ut0YfmDVyvN/h7qO62v9HkblD9otx7HhwPxwfxN + ekV5VSHLJo9ilPngmHeijtRUGpl67/yImtf9eUn8q+fB0FfQeu/8iNrX/XlJ + /KvnwdBXbhPhZ52N+JBRRRXWcYV9p/sNfDr/AISr9qa+8bXsHmaR4Rs90JYA + q19cKyRjB/uRea3sWSvip3WOF5HO1EUsx9AOTX7kfsofDhvhx+xf4bt7238j + X9bB1nVg33lknAKRnv8AJEIkx2KmvieP82+pZTKMX71T3V6P4vw0+aPuPD7K + PrubRnJe7T95+q2/HX5M89+Mv7FfgPx415rvgJ4Ph54ukJkdLeHOmXj8n97A + MeWxOPnjx3yrV+Y/jT4WePvhP8TNM0nx54eudIaW+jWzv0Pm2V786/6qcDaT + gg7Dhxnla/dXxv8AEXwf8O/D39o+K9ZgsN4P2e1X95c3JH8McQ+Zj74wO5Ff + n78Wf2ifEHxH0q98O6bplpoHg2f5ZYLmJLi7u1BBBdiCsXIBAT5h/fr5HgbO + c9qWhKPPR7ydrej1b9LP1R9jx1kuRQvOMuSt2jrf1WiXrdejPnqX/j6k/wB4 + /wA6Z29aPxor9MPzMPWiiigD6V/Zd8Ff8JJ+0B/b11D5mm+HohckkZU3DZWE + fUYdx7xiv0srwz9njwV/wh37NmlNcReXqmsf8TG7yPmUOB5aeoxGF47MWr3O + v574wzT69mc2n7sfdXy3+93P6F4Pyv6jlkE170vefz2+5WCiiivlz6gKKKKA + CiiigAooooAKKKKACiiigAryz41/D6L4ofsyeLPB5RGvrm0Mumu2Bsuo/nhO + ewLKFJ/us3rXqdFb4bETw9aNWm7Si016owxWGp4ijOlUV4yTT9GfzVyxSwXM + kM0bwzRsVkjdcMrA4IIPQio6+qP2wfhz/wAIH+2DqmoWcBi0TxMn9q2hA+VZ + WJFwmfXzMvjsJFr5Xr+r8sx8MbhKeIhtJJ/8D5PQ/krNMBUwWLqYee8G1/k/ + mtQoooruOEKKKKANLRtROk+KbHUeSkUn70f3ozw4/I5/CvoY4zlSGU4Kt/eB + 5Br5n6jnkV7b4N1L+0PAsCO265sm+zyknkgDKH8V4/CuTFQ0Ujtwc7PlOqoo + oriPQMzXP+RH1r/ryk/lXz2Ogr6E1z/kR9a/68pP5V89joK7cJ8LPOxnxIKK + KK6zjPT/AINeENO8bftJeGdJ165tbHwrazf2n4hurqQRxR2VuRJIrMem9tkY + 9fMr9IPiT+1nI5n0j4W2iiMZQ+INQg4+sEDdfZpMDn7pr85/AGmiLQbvVJky + 91II4cj+BDkn8W7/AOzXoHU18pm+SYbH4uNXELmUFZR6X6t93su2mzPrsnzr + FYDBypYd8rm7uXW3RJ9Fv567lzUtS1LWfENxq+s6je6xq1wf315eTGSV/bJ6 + D0UYA7CqdHr2or0oxUUklZI89tyd3uw9qO1FB6GmIK9I+EvgxvHnx98P+H3j + aSwM/n6gewt4/mcE9t2AgPqwrzev0A/ZI8Ff2f4A1jxzdw7bnVJPslixHIgj + b52B9Gk4P/XIV4HE+afUMuqVU/eei9X/AJb/ACPf4Yyv6/mNOk17q1fov89v + mfYCqqoFUBVAwABgAUtFFfzkf0cFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAF + FFFAHyP+2d8OP+E2/ZHutes4PN1rwpKdRhKrlmtiNtyvsNmJD/1xFfi/X9KN + 1a299plzZXkMdzaXETRTxSLlZEYEMpHcEEiv59fi54CuPhn+0b4s8FzCQw6f + fN9ikfrLbPh4XJ9TGy59Dkdq/bPC7N+ehUwUnrH3l6Pf7nr8z8Q8U8n5K9PG + wWkvdfqtvvWnyPOKKKK/WD8kCiiigArsfA2o/YvGq2jtiC/TyTk8CQcofzyP + xrjqVWdJFkiYpKjBkb0YHIP51M480WioT5ZJn0tQKp6dfpqnh+z1GPhbiIOw + /ut0YfgQa6zwz4U8R+MtfbTfDGlT6rcJjz5AQkFsP70sp+VB9efQGvFrVYUY + OdSSilu3ol8z3qFKdaajTTbeyWrfojjdbOPBOs/9eUn8q+eh0FfrN4W/Zz8J + 6foNx/wnDjxjfXMDRTWsbPBZQhhg7MEO7Y6OSMHkLXzj8T/2ONc0o3Gr/Ce/ + m8UaaMu3h/UJVXUIh1xDKcJcD0Vtrn/arwMv47yepiHQ9pbtJq0X8+nq0l5n + u5lwJnFPDqv7O/eKd5L5dfk2+58T1LDBLdXsNrbqWnmkEcYHqTgUt1bXVhrF + 1p2oWl3p+o2rmO5tLqFopoGH8LowBU/UV2vgHTvtPiifUnXMVlH+79DK4IH4 + hcn8RX3EppQ5j4aFNufKz1W1tYbDS7axgA8m2iEaY74HX8Tk/jU9H40nO8fS + vKPYQUveiigYe+KKKKBGnoukXuv+MNM0PTo/Nv7+6jtoF7bnYKM+3PJ7Cv2T + 8N6DZeF/AOj+HdOGLPTrRLeIkYLbRgsfcnJPuTXwX+yb4L/tb4saj4xu4t1l + osHlWpYcNcSgjI9dse/PoXU1+h9fjPiJmntcXHCxekNX6v8AyX5s/aPDrK/Z + YSWKktZ6L0X+b/JBRRRX5yfowUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUU + UAFfm/8At9fDnztI8LfFKwgy9uf7J1cqP4GLPA59AG8xST/fQV+kFcR8SPBV + l8RfgV4o8FX5RYdWsHhjkcZEMo+aKTH+xIqN/wABr3OG82eXZjSxHROz9Ho/ + 8/U8LiXKVmWW1cP1auvVar/L0P53qu6dYy6nrltp8DxRzzsVjaQ4XOCeT26U + up6be6P4k1DSNSt3tNRsbmS2uoH+9FIjFXU+4IIrT8J/8lK0b/rv/wCytX9S + ua5OZH8rKHv8sjKvtPvdMv8A7LqFtLaz9g44ceqnow+lU6+kLq1tb/T2tL63 + hu7dv+Wcq5APqO4PuK801jwBcRu02gu96h5+xyn96P8Acbo/0OD9axp4lPSW + htVwso6x1R51RSsrJIyOpV1JDKeoI6ikrpOVnvfwL1f4ax+Ln0f4q6vqmkaC + 0olsprcbbcyH7yXEgy8cZwDuUeuSK/WTR7LRtP8ABun2/hqDSrfw4ybrH+y9 + ptZBj7ysvDH1JJb1r8IK9U+Gnxm8ffCnU93hfVt+kO4a60W+BmsrjnJzGT8j + dfmTBycnNfn/ABjwfXzX95RrNNfZfw+q7Pzd/kfoPBnGVDKX7OtRTT+0viXk + +68tPmfsy33OxpSPXkV88/Df9pr4a/EDTlttVvYPAPiVY90tjqtx/o0uBljB + cHhuhOxsNgVgeOv2i0iefTPh7aebICVbXNQh+Ue8EB6+zycdwtfj1HhPNqmJ + eH9i1Jbt6Jed9n8r36H7LW4uymnhViPbKSeyWsn5W3Xzsu53/wAbfC3wf8Qe + CEuvi1DbW90Iium6laNs1gccCAqC0i/7Lho/pX5/6bo+n6FaXFhpU97d2P2q + SSG4vIkjuJUJ+UyKhKhgoAwpIra1HUNQ1jXrjVNYv7zVdTnOZru7lMkj+2T0 + HsMAdhVTvX7Rw3kdTK8N7KVZzv0+yv8ACunn33sj8W4kzunmmJ9rGioW6/af + +J7P7tNrsPek/wCWg+lL3qhqOo2Wk6Y1/qMxtrRCFaTYW+Y/dXjucHGcZr6F + anzzdi/39axdT8Q6XpN3FazzGa+kkVFtYcM67iBluyjnvz7V53rXjm/vd9vp + avpVoeDJnM8g+vRB7Dn3rj7H/kOWR6k3UZJPJJ3jk+tdUMM95HHUxa2ifSDA + rIynkg4yBTakl/4+ZP8AeP8AOvYfgN4K/wCE2/aR0W0uIvN0rT2/tC/BGQUi + IKqfUM5RSPQn0ry8Zi4YbDzrT2imz18FhJ4rEQow3k0vvP0H+C3gr/hBP2dt + B0iaLytTnj+2aiCMN58oBKn3Vdqf8Ar1Wiiv5lxeJnia86095Nt/M/pvCYWG + GoQow2ikl8gooornOgKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiig + D8df23fhx/wiX7UUXi6xg8vSPFdubhiowq3ce1Jx/wACBjkz3Lt6V8peE/8A + kpWjf9d//ZWr9pf2rfhz/wALF/Y71+O0g87W9DH9r6dtHzMYlbzUHc7ojIAv + dtvpX4g2l3cWOoxXdpKYLqI5jkABKnBGRnvzX9GcB5t9fyiMJP3qfuv06P7t + Pkz+buPco+oZxKcV7tT3l6/aX36+jR71q2t6ZokAbULjZKwzHbxjdLJ9F7D3 + OBXlOteMdU1ZXt4CdM09uDFE/wC8cf7b9fwGB9a5V3eSd5ZXeWVzl5HYszH1 + JPWm19jToRjq9WfHVcTKe2iCiiitznCiiigAIDAhgGB6gius0XxhqmkqkE5O + qaeOBDM/zoP9h+o+hyK5QdaSplFSVmVGbi7pn0FpOt6ZrcG7T590yjMltINs + qf8AAe49xkVroju+1FLt1OOw9T6V80I7xzpLE7xSoco6MVZT6gjpX0B4fv7u + /wDh9pM15N5sskO6VtoBkbcRubHU4A59q4K9Hk1R6WHxHO7M1yAOjBj3K9Pz + 719Ffs3WlnqPjLx9p2pWVlqenXOhQx3NneQLNDOv2j7rowIYfX8K+c6+k/2Y + /wDkpfjT/sCw/wDpQK+U4vbWTV2uy/8ASon1vCEVLOaCa3b/APSWYvxR/Y40 + rURc6z8JL2HQb7ln8N6lOTZSnri3nOWhPokm5P8AaWvhHVPDniDwh8TIPD3i + rRdS8O65BcxmSzvoTG5G8fMp6Oh7MpIPrX7h319Y6VodxqeqX1npem24zPd3 + cojiT6se/sMk9ga+OvjJ8XPCnjrww3hTS/C+n+ItPik3Qa3rNsQ9s4Od9mvD + xngfOxAP9w18twVxVnFaaoVIOrBbyejj6yekvR+8+59Pxvwnk1CDr05qlUe0 + VqpeiWsfX4fI+epf+PmT/fP86/Rr9lXwV/YPwQufFF3Ft1DX590RYcrbRkqn + 03MXb3BWvgXwh4cvPGPxR0Pw1ZFvtOo3iwmTbny1Jy8h9lUMx9ga/ZDTNOtN + I8O2GlafEILGyt0t7eIdERFCqPwAFaeIuaeyw0MJF6z1fov83+RPhzlftcTP + FyWkNF6vf7l+Zdooor8cP2QKKKKACiiigAooooAKKKKACiiigAooooAKKKKA + CiiigAooooAQgMpDAEEYIPevwP8A2gfh0fhd+1d4q8MwwGHSGn+2aRxhTazZ + dFHqEO6PPrGa/fGvgX9vL4cf2z8HtC+JFhb7r7QJ/smpMq8taTMAjE+iS4AH + /TZjX3vh3m/1PNFSk/dq+78/s/5fM+A8Rsn+uZW6sV71L3vl9r/P5H5Q0UUV + /Q5/OgUUUUAFFdT4P8D+MPiB4wj0DwT4c1PxLqzYLxWsfyQKTgPNIcJEmeNz + kD0zX6U/Bz9hbw9ootdd+MF3B4t1cYdNAtGZdNtz1xK3DXDDjrtTI+6w5rwM + 74mwGVQvXn73SK1k/l09XZH0GR8MZhmsrUIe71k9Ir59fRXZ+dfhX4Y+OfGX + gDxF4u0PQZ28I6HYT3uo63dHybQJChd44nb/AF0uBwiZ5IyRXAA5UH1r98Pj + rZ2em/sE/FGx0+0trCxt/CN5HBb28QjjiUQMAqquAAPQV+B6/wCrX6Vw8J8R + 1M5p1asoKKjKyW+lur7/ACR3cXcN08mqUaUZuTlG7e2t7aLsLXu/hT/km2i+ + 8B/9DavCK938Kf8AJNdG/wCuB/8AQ2r6TFfCj5vB/G/Q6DHNekfDX4ht8N9U + 8SalBpK6xf3+nx2tpHLKY4Y2WTeXkI+YjHQLyT3Feb96K8fGYOjiqMqNZXi9 + 16O/T0PcweLq4WtGtRdpR2fyt1Ol8VeMPE3jbXFv/E+qy6i8Zzb2yr5dtbD0 + iiHyr9eWPc1zVH+TUkUUk9zFBBG8s0jBI0RSWZicAAdya0o0adGmoU4qMVsl + okZ1q1StUc6knKT3b1bPsv8AZF8FfaPEeu+PLuEGKzT7Bp7MP+WrANKw9Cqb + V+kjV95Vwnwz8Hx+BPgf4e8NBUFzbWwa8Zed87/PIc9xuJA9gK7uv514kzT6 + /mFSsn7uy9Ft9+/zP6L4byv+z8up0Wvetd+r3+7b5BRRRXhHuhRRRQAUUUUA + FFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABUNzbW95p89peW8N1azIUmh + mQOkikYKsp4II7GpqKabTuDVz4O+Mf7EPhbxOLrXPhdPb+D9cbLtpU2Tp9wf + RMZaAk+m5ewVetfmX44+HvjL4ceMX0LxpoN9od+MmLzlzHOoON8cgysi+6k1 + /RLXNeK/B3hfxz4Rn0Hxdoen6/pMv3oLuPdtP95W6ow7MpBHrX6Hw/4h43BW + p4n95D/yZfPr8/vR+dcQ+HWCxt6mG/dz/wDJX6rp6r7mfzmUV+hnxi/YZ1fS + vteu/CO8k13Txl30G+kAuohySIpDhZR6K2G93NfAGo6bqOj65daZq1jeaZqV + tIY7i1uoWiliYdVZWAIPsa/acoz3BZnT58NO/ddV6r+kfiWb5DjssqcmJhbs + +j9H/TNTwv4u8VeCfFsGveD/ABDqvhvV4iMXFjMV3gHO2RD8siequCPav0g+ + Dv7dul34tdC+M9jDoF7gIviXTomNlKcYzPFy0BPdl3Jk/wAAr8vqKxzrhvAZ + pC2Ihr0ktJL5/o7ryNsk4lx+VTvh56dYvWL+X6qz8z96/jjqWnax+wF8UNT0 + m/stU0258JXr293aTrLDMphb5ldSQw9wa/BJf9Wv0r0PwL478YeGrLU/B2ia + /eWfhXxLBJp2saS4EttLHMux3SNsiOYA8SJg565rI1nwfqmjo80QOpaev/Le + FTvQf7adR9RkV5vCvD7yWFWjKfMpO6ezta2vn8z0uLOIVnc6VeMHFxVmt1e9 + 9PL5HK17v4T/AOSa6N/1wP8A6G1eDgggEEEHoQa948KD/i2ujf8AXA/+htX0 + uL+FHzWDfvv0Ogorq/CXgjxR468RDTPDGkXWpzjHmug2xQg/xO5+VR9Tz2zX + 3V8Nv2XPDnh0W+qeN5IfFGsDDCzAP2KE+hB5l/4Fhf8AZ718lnPEmBy2P72V + 5fyrf/gfM+xybhvHZlL91G0f5nov+D8j5C+HfwX8b/Ei5jm0ux/s/RN2JNVv + QUgHrs7yH2Xj1Ir7++HHwI8EfDsQ3sVt/bviJME6pfICyN6xJyI/qMt/tGva + Ioo4LaOGGNIYY1CpGihVUAYAAHQCn1+P55xjjsxvBPkh2XX1fX8F5H7DkfBu + By602uefd9PRdPxfmFFFFfJH1oUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUU + UUAFFFFABRRRQAUUUUAFFFFABRRRQAV5L8Ufgj8O/i7obW/i7RI21JY9ttq9 + piK9t/TbJj5h/suGX2r1qit8Niq2HqKpSk4yXVaHPisLRxNJ060VKL6PVH4s + fGP9kX4ifDIXWsaJHJ448IxksbywhP2m2X/ptCMnAHV13L3O3pXydX9LFfK/ + xj/ZL+HXxSF1q2mQp4K8Xvlv7R0+EeTct/03h4Dc9WXa3qT0r9a4f8TNqWYL + /t9fqv1X3H5HxD4Yb1cvf/bj/R/o/vPxl0H/AJHvRf8Ar9j/AJ19B5YPlSQc + 8EVzvjX4FfEb4QfFTR4vFWjPJpLagi22s2OZbOf5uPnx8jH+64VvYjmvrD4b + /s1+MPGbQalr6yeE/D74YSXEf+lTr/sRHoCP4mx6gNX3+YZ5gKdCOJlVXI1o + 73v6d35HwGW5FmFSvLDKk+dbq1reb7LzPlG4+Hsfi3W4rXRLG5TX7l9sSWMB + k89vRo16/VcGvtv4M/si6jB4O0ib4o3S2fkR86Pp8253+Yn95KPujn7q5P8A + tA19keB/hp4O+Hmj/ZfDOkx287qFnvpv3lzP/vyHnH+yML6Cu8r8oz3xDxOI + TpYT3I938Xy7fi/Q/Wci8OsNh5Kti/fl2Xw/Pv8AgvJmPoegaL4Z8Ow6ToGm + Wek6dF9yC2jCrnuT3LHuTknvWxRRX5xOcpycpO7Z+kQhGEVGKskFFFFSUFFF + FABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUU + AFFFFABRRRQAUUUUAYHib/kUZP8Ar5t//R8db9FFbS/hL1f6GS/iv0X6hRRR + WJqFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB//2Q== + </data> + </array> + <key>name</key> + <array> + <string>vagrant</string> + </array> + <key>passwd</key> + <array> + <string>********</string> + </array> + <key>passwordpolicyoptions</key> + <array> + <data> + PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU + WVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO + IiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w + LmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+ZmFp + bGVkTG9naW5Db3VudDwva2V5PgoJPGludGVnZXI+MDwvaW50ZWdlcj4KCTxr + ZXk+ZmFpbGVkTG9naW5UaW1lc3RhbXA8L2tleT4KCTxkYXRlPjIwMDEtMDEt + MDFUMDA6MDA6MDBaPC9kYXRlPgoJPGtleT5sYXN0TG9naW5UaW1lc3RhbXA8 + L2tleT4KCTxkYXRlPjIwMDEtMDEtMDFUMDA6MDA6MDBaPC9kYXRlPgoJPGtl + eT5wYXNzd29yZExhc3RTZXRUaW1lPC9rZXk+Cgk8ZGF0ZT4yMDE0LTAzLTA2 + VDE4OjU0OjQ1WjwvZGF0ZT4KPC9kaWN0Pgo8L3BsaXN0Pgo= + </data> + </array> + <key>realname</key> + <array> + <string>vagrant</string> + </array> + <key>shell</key> + <array> + <string>/bin/bash</string> + </array> + <key>uid</key> + <array> + <string>501</string> + </array> +</dict> +</plist> diff --git a/knife/spec/data/mac_users/10.9.shadow.xml b/knife/spec/data/mac_users/10.9.shadow.xml new file mode 100644 index 0000000000..b8359d080a --- /dev/null +++ b/knife/spec/data/mac_users/10.9.shadow.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>SALTED-SHA512-PBKDF2</key> + <dict> + <key>entropy</key> + <data> + EmAakNsXy/i6SAjmOC+w07nYpsGhkEd79oCrIa+2BlRnE25VzCCKb3QVbj2v + IPsTNp70t7r6BH2ANZ+0akikrczVSOuzOFGwk0fMqENBp/k6JxRzQ/ifuEP7 + RsABfSZK+kl2uqz5QbkVvR7ByiTDCz51ngJAPgL1n+f/WTinY2w= + </data> + <key>iterations</key> + <integer>34482</integer> + <key>salt</key> + <data> + 7pVL5HL9xg3fiUhHgUM5k2JfAGr27IEMCPSafkE5RqE= + </data> + </dict> +</dict> +</plist> diff --git a/knife/spec/data/metadata/quick_start/metadata.rb b/knife/spec/data/metadata/quick_start/metadata.rb new file mode 100644 index 0000000000..e7ae9d1749 --- /dev/null +++ b/knife/spec/data/metadata/quick_start/metadata.rb @@ -0,0 +1,14 @@ +maintainer "Chef Software, Inc." +maintainer_email "cookbooks@chef.io" +license "Apache 2.0" +description "Example cookbook for quick_start wiki document" +version "0.7" + +%w{ + redhat fedora centos + ubuntu debian + macosx freebsd openbsd + solaris +}.each do |os| + supports os +end diff --git a/knife/spec/data/mixin/invalid_data.rb b/knife/spec/data/mixin/invalid_data.rb new file mode 100644 index 0000000000..e6f6c3a783 --- /dev/null +++ b/knife/spec/data/mixin/invalid_data.rb @@ -0,0 +1,3 @@ +# For spec/functional/mixin/from_file_spec.rb +a :foo +c :bar diff --git a/knife/spec/data/mixin/real_data.rb b/knife/spec/data/mixin/real_data.rb new file mode 100644 index 0000000000..e15b86fc68 --- /dev/null +++ b/knife/spec/data/mixin/real_data.rb @@ -0,0 +1,2 @@ +# For spec/functional/mixin/from_file_spec.rb +a :foo diff --git a/knife/spec/data/nested.json b/knife/spec/data/nested.json new file mode 100644 index 0000000000..775bb21981 --- /dev/null +++ b/knife/spec/data/nested.json @@ -0,0 +1,2 @@ +{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":"test" +}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} diff --git a/knife/spec/data/nodes/default.rb b/knife/spec/data/nodes/default.rb new file mode 100644 index 0000000000..1d6291f166 --- /dev/null +++ b/knife/spec/data/nodes/default.rb @@ -0,0 +1,15 @@ +## +# Nodes should have a unique name +## +name "test.example.com-default" + +## +# Nodes can set arbitrary arguments +## +default[:sunshine] = "in" +default[:something] = "else" + +## +# Nodes should have recipes +## +run_list "operations-master", "operations-monitoring" diff --git a/knife/spec/data/nodes/test.example.com.rb b/knife/spec/data/nodes/test.example.com.rb new file mode 100644 index 0000000000..b30e8489e0 --- /dev/null +++ b/knife/spec/data/nodes/test.example.com.rb @@ -0,0 +1,17 @@ +## +# Nodes should have a unique name +## +name "test.example.com" + +## +# Nodes can set arbitrary arguments +## +normal[:sunshine] = "in" +normal[:something] = "else" + +## +# Nodes should have recipes +## +run_list "operations-master", "operations-monitoring" + +chef_environment "dev" diff --git a/knife/spec/data/nodes/test.rb b/knife/spec/data/nodes/test.rb new file mode 100644 index 0000000000..e1301130d2 --- /dev/null +++ b/knife/spec/data/nodes/test.rb @@ -0,0 +1,15 @@ +## +# Nodes should have a unique name +## +name "test.example.com-short" + +## +# Nodes can set arbitrary arguments +## +default[:sunshine] = "in" +default[:something] = "else" + +## +# Nodes should have recipes +## +run_list "operations-master", "operations-monitoring" diff --git a/knife/spec/data/null_config.rb b/knife/spec/data/null_config.rb new file mode 100644 index 0000000000..8865745632 --- /dev/null +++ b/knife/spec/data/null_config.rb @@ -0,0 +1 @@ +$__KNIFE_INTEGRATION_FAILSAFE_CHECK << " ole" diff --git a/knife/spec/data/object_loader/environments/test.json b/knife/spec/data/object_loader/environments/test.json new file mode 100644 index 0000000000..744819c60a --- /dev/null +++ b/knife/spec/data/object_loader/environments/test.json @@ -0,0 +1,7 @@ +{ + /* testing that we support c-style comments */ + // testing that we support c++-style comments as well + "name": "test", + "description": "prod", + "run_list": [] +} diff --git a/knife/spec/data/object_loader/environments/test.rb b/knife/spec/data/object_loader/environments/test.rb new file mode 100644 index 0000000000..8bf4ee52e0 --- /dev/null +++ b/knife/spec/data/object_loader/environments/test.rb @@ -0,0 +1,2 @@ +name "test" +description "prod" diff --git a/knife/spec/data/object_loader/environments/test_json_class.json b/knife/spec/data/object_loader/environments/test_json_class.json new file mode 100644 index 0000000000..f51943f60e --- /dev/null +++ b/knife/spec/data/object_loader/environments/test_json_class.json @@ -0,0 +1,8 @@ +{ + /* testing that we support c-style comments */ + // testing that we support c++-style comments as well + "name": "test", + "json_class": "Chef::Environment", + "description": "prod", + "run_list": [] +} diff --git a/knife/spec/data/object_loader/nodes/test.json b/knife/spec/data/object_loader/nodes/test.json new file mode 100644 index 0000000000..a370d6378d --- /dev/null +++ b/knife/spec/data/object_loader/nodes/test.json @@ -0,0 +1,7 @@ +{ + /* testing that we support c-style comments */ + // testing that we support c++-style comments as well + "name": "test", + "environment": "prod", + "run_list": [] +} diff --git a/knife/spec/data/object_loader/nodes/test.rb b/knife/spec/data/object_loader/nodes/test.rb new file mode 100644 index 0000000000..9629539859 --- /dev/null +++ b/knife/spec/data/object_loader/nodes/test.rb @@ -0,0 +1,2 @@ +name "test" +environment "prod" diff --git a/knife/spec/data/object_loader/nodes/test_json_class.json b/knife/spec/data/object_loader/nodes/test_json_class.json new file mode 100644 index 0000000000..3c5b12ce6c --- /dev/null +++ b/knife/spec/data/object_loader/nodes/test_json_class.json @@ -0,0 +1,8 @@ +{ + /* testing that we support c-style comments */ + // testing that we support c++-style comments as well + "name": "test", + "json_class": "Chef::Node", + "environment": "prod", + "run_list": [] +} diff --git a/knife/spec/data/object_loader/roles/test.json b/knife/spec/data/object_loader/roles/test.json new file mode 100644 index 0000000000..744819c60a --- /dev/null +++ b/knife/spec/data/object_loader/roles/test.json @@ -0,0 +1,7 @@ +{ + /* testing that we support c-style comments */ + // testing that we support c++-style comments as well + "name": "test", + "description": "prod", + "run_list": [] +} diff --git a/knife/spec/data/object_loader/roles/test.rb b/knife/spec/data/object_loader/roles/test.rb new file mode 100644 index 0000000000..8bf4ee52e0 --- /dev/null +++ b/knife/spec/data/object_loader/roles/test.rb @@ -0,0 +1,2 @@ +name "test" +description "prod" diff --git a/knife/spec/data/object_loader/roles/test_json_class.json b/knife/spec/data/object_loader/roles/test_json_class.json new file mode 100644 index 0000000000..f975c8be43 --- /dev/null +++ b/knife/spec/data/object_loader/roles/test_json_class.json @@ -0,0 +1,8 @@ +{ + /* testing that we support c-style comments */ + // testing that we support c++-style comments as well + "name": "test", + "json_class": "Chef::Role", + "description": "prod", + "run_list": [] +} diff --git a/knife/spec/data/old_home_dir/my-dot-emacs b/knife/spec/data/old_home_dir/my-dot-emacs new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/knife/spec/data/old_home_dir/my-dot-emacs diff --git a/knife/spec/data/old_home_dir/my-dot-vim b/knife/spec/data/old_home_dir/my-dot-vim new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/knife/spec/data/old_home_dir/my-dot-vim diff --git a/knife/spec/data/partial_one.erb b/knife/spec/data/partial_one.erb new file mode 100644 index 0000000000..9fd79a0d65 --- /dev/null +++ b/knife/spec/data/partial_one.erb @@ -0,0 +1 @@ +partial one <%= render('test.erb', :cookbook => 'openldap').strip %> calling home diff --git a/knife/spec/data/prefer_metadata_json/metadata.json b/knife/spec/data/prefer_metadata_json/metadata.json new file mode 100644 index 0000000000..eff8836a3b --- /dev/null +++ b/knife/spec/data/prefer_metadata_json/metadata.json @@ -0,0 +1,51 @@ +{ + "name": "prefer_metadata_json", + "description": "", + "long_description": "", + "maintainer": null, + "maintainer_email": null, + "license": "All rights reserved", + "platforms": { + + }, + "dependencies": { + + }, + "recommendations": { + + }, + "suggestions": { + + }, + "conflicting": { + + }, + "providing": { + + }, + "replacing": { + + }, + "attributes": { + + }, + "groupings": { + + }, + "recipes": { + + }, + "version": "1.2.3", + "source_url": "", + "issues_url": "", + "privacy": false, + "chef_versions": [ + + ], + "ohai_versions": [ + + ], + "gems": [ + + ] +} diff --git a/knife/spec/data/prefer_metadata_json/metadata.rb b/knife/spec/data/prefer_metadata_json/metadata.rb new file mode 100644 index 0000000000..a46aa29a5c --- /dev/null +++ b/knife/spec/data/prefer_metadata_json/metadata.rb @@ -0,0 +1,6 @@ +# these deliberately do not match metadata.json +name "test" +version "0.0.1" + +# this raises hard if anything even tries to parse it +raise "TEH SADNESS" diff --git a/knife/spec/data/prefer_metadata_json/recipes/default.rb b/knife/spec/data/prefer_metadata_json/recipes/default.rb new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/knife/spec/data/prefer_metadata_json/recipes/default.rb diff --git a/knife/spec/data/recipes.tgz b/knife/spec/data/recipes.tgz Binary files differnew file mode 100644 index 0000000000..a6c172a001 --- /dev/null +++ b/knife/spec/data/recipes.tgz diff --git a/knife/spec/data/recipes/test.rb b/knife/spec/data/recipes/test.rb new file mode 100644 index 0000000000..c33d714a2c --- /dev/null +++ b/knife/spec/data/recipes/test.rb @@ -0,0 +1,7 @@ + +file "/etc/nsswitch.conf" do + action :create + owner "root" + group "root" + mode 0644 +end diff --git a/knife/spec/data/remote_directory_data/remote_dir_file.txt b/knife/spec/data/remote_directory_data/remote_dir_file.txt new file mode 100644 index 0000000000..17aa3a6838 --- /dev/null +++ b/knife/spec/data/remote_directory_data/remote_dir_file.txt @@ -0,0 +1 @@ +I'm a file inside a the root remote_directory source dir.
\ No newline at end of file diff --git a/knife/spec/data/remote_directory_data/remote_subdirectory/remote_subdir_file.txt b/knife/spec/data/remote_directory_data/remote_subdirectory/remote_subdir_file.txt new file mode 100644 index 0000000000..a73728a4bb --- /dev/null +++ b/knife/spec/data/remote_directory_data/remote_subdirectory/remote_subdir_file.txt @@ -0,0 +1 @@ +I'm a file in a subdirectory inside a remote_directory source
\ No newline at end of file diff --git a/knife/spec/data/remote_file/nyan_cat.png b/knife/spec/data/remote_file/nyan_cat.png Binary files differnew file mode 100644 index 0000000000..14cd6acf2a --- /dev/null +++ b/knife/spec/data/remote_file/nyan_cat.png diff --git a/knife/spec/data/remote_file/nyan_cat.png.gz b/knife/spec/data/remote_file/nyan_cat.png.gz Binary files differnew file mode 100644 index 0000000000..efa9d4427a --- /dev/null +++ b/knife/spec/data/remote_file/nyan_cat.png.gz diff --git a/knife/spec/data/root_alias_cookbooks/dup_attr/attributes.rb b/knife/spec/data/root_alias_cookbooks/dup_attr/attributes.rb new file mode 100644 index 0000000000..3a3bab96e1 --- /dev/null +++ b/knife/spec/data/root_alias_cookbooks/dup_attr/attributes.rb @@ -0,0 +1 @@ +default["aliased"]["attr"] = "value" diff --git a/knife/spec/data/root_alias_cookbooks/dup_attr/attributes/default.rb b/knife/spec/data/root_alias_cookbooks/dup_attr/attributes/default.rb new file mode 100644 index 0000000000..a6f6c78bb0 --- /dev/null +++ b/knife/spec/data/root_alias_cookbooks/dup_attr/attributes/default.rb @@ -0,0 +1 @@ +default["aliased"]["attr"] = "other" diff --git a/knife/spec/data/root_alias_cookbooks/dup_attr/metadata.rb b/knife/spec/data/root_alias_cookbooks/dup_attr/metadata.rb new file mode 100644 index 0000000000..703a73ab19 --- /dev/null +++ b/knife/spec/data/root_alias_cookbooks/dup_attr/metadata.rb @@ -0,0 +1,2 @@ +name "dup_attr" +version "1.0.0" diff --git a/knife/spec/data/root_alias_cookbooks/dup_attr/recipe.rb b/knife/spec/data/root_alias_cookbooks/dup_attr/recipe.rb new file mode 100644 index 0000000000..d82e58fbcd --- /dev/null +++ b/knife/spec/data/root_alias_cookbooks/dup_attr/recipe.rb @@ -0,0 +1,3 @@ +ruby_block "root alias" do + block { } +end diff --git a/knife/spec/data/root_alias_cookbooks/dup_recipe/attributes.rb b/knife/spec/data/root_alias_cookbooks/dup_recipe/attributes.rb new file mode 100644 index 0000000000..3a3bab96e1 --- /dev/null +++ b/knife/spec/data/root_alias_cookbooks/dup_recipe/attributes.rb @@ -0,0 +1 @@ +default["aliased"]["attr"] = "value" diff --git a/knife/spec/data/root_alias_cookbooks/dup_recipe/metadata.rb b/knife/spec/data/root_alias_cookbooks/dup_recipe/metadata.rb new file mode 100644 index 0000000000..62273a64d5 --- /dev/null +++ b/knife/spec/data/root_alias_cookbooks/dup_recipe/metadata.rb @@ -0,0 +1,2 @@ +name "dup_recipe" +version "1.0.0" diff --git a/knife/spec/data/root_alias_cookbooks/dup_recipe/recipe.rb b/knife/spec/data/root_alias_cookbooks/dup_recipe/recipe.rb new file mode 100644 index 0000000000..d82e58fbcd --- /dev/null +++ b/knife/spec/data/root_alias_cookbooks/dup_recipe/recipe.rb @@ -0,0 +1,3 @@ +ruby_block "root alias" do + block { } +end diff --git a/knife/spec/data/root_alias_cookbooks/dup_recipe/recipes/default.rb b/knife/spec/data/root_alias_cookbooks/dup_recipe/recipes/default.rb new file mode 100644 index 0000000000..3eb7c22809 --- /dev/null +++ b/knife/spec/data/root_alias_cookbooks/dup_recipe/recipes/default.rb @@ -0,0 +1,3 @@ +ruby_block "other" do + block { } +end diff --git a/knife/spec/data/root_alias_cookbooks/simple/attributes.rb b/knife/spec/data/root_alias_cookbooks/simple/attributes.rb new file mode 100644 index 0000000000..3a3bab96e1 --- /dev/null +++ b/knife/spec/data/root_alias_cookbooks/simple/attributes.rb @@ -0,0 +1 @@ +default["aliased"]["attr"] = "value" diff --git a/knife/spec/data/root_alias_cookbooks/simple/metadata.rb b/knife/spec/data/root_alias_cookbooks/simple/metadata.rb new file mode 100644 index 0000000000..9147558459 --- /dev/null +++ b/knife/spec/data/root_alias_cookbooks/simple/metadata.rb @@ -0,0 +1,2 @@ +name "simple" +version "1.0.0" diff --git a/knife/spec/data/root_alias_cookbooks/simple/recipe.rb b/knife/spec/data/root_alias_cookbooks/simple/recipe.rb new file mode 100644 index 0000000000..d82e58fbcd --- /dev/null +++ b/knife/spec/data/root_alias_cookbooks/simple/recipe.rb @@ -0,0 +1,3 @@ +ruby_block "root alias" do + block { } +end diff --git a/knife/spec/data/rubygems.org/latest_specs.4.8.gz b/knife/spec/data/rubygems.org/latest_specs.4.8.gz Binary files differnew file mode 100644 index 0000000000..ab6a175f32 --- /dev/null +++ b/knife/spec/data/rubygems.org/latest_specs.4.8.gz diff --git a/knife/spec/data/rubygems.org/nonexistent_gem b/knife/spec/data/rubygems.org/nonexistent_gem Binary files differnew file mode 100644 index 0000000000..0ba94359df --- /dev/null +++ b/knife/spec/data/rubygems.org/nonexistent_gem diff --git a/knife/spec/data/rubygems.org/nonexistent_gem-info b/knife/spec/data/rubygems.org/nonexistent_gem-info new file mode 100644 index 0000000000..7e88a0e205 --- /dev/null +++ b/knife/spec/data/rubygems.org/nonexistent_gem-info @@ -0,0 +1 @@ +This gem could not be found
\ No newline at end of file diff --git a/knife/spec/data/rubygems.org/sexp_processor b/knife/spec/data/rubygems.org/sexp_processor Binary files differnew file mode 100644 index 0000000000..37c6e97769 --- /dev/null +++ b/knife/spec/data/rubygems.org/sexp_processor diff --git a/knife/spec/data/rubygems.org/sexp_processor-4.15.1.gemspec.rz b/knife/spec/data/rubygems.org/sexp_processor-4.15.1.gemspec.rz Binary files differnew file mode 100644 index 0000000000..38840f2682 --- /dev/null +++ b/knife/spec/data/rubygems.org/sexp_processor-4.15.1.gemspec.rz diff --git a/knife/spec/data/rubygems.org/sexp_processor-info b/knife/spec/data/rubygems.org/sexp_processor-info new file mode 100644 index 0000000000..78add7f2fa --- /dev/null +++ b/knife/spec/data/rubygems.org/sexp_processor-info @@ -0,0 +1,49 @@ +--- +3.0.0 |checksum:ff9abf0d904ba57b9654352b396aa28cf6ad5315af99d8bbf664f5ff6efd3a5d +3.0.1 |checksum:d012a759dc6950dcda667a359051c2e62e4bd19790aeed698a5e47d013ef3ae7 +3.0.2 |checksum:ba85e835493e6099c2f52937b77ce518d65af39409befdd9b43927c0a604ed87 +3.0.3 |checksum:a433b01d821f5e81200fbec77fc26a1ecb186ad7d8e40d19ed34ea295287170a +3.0.4 |checksum:5f394545863d5abe5c3f53c3b883128f58900cd792504076a765b53c9a49f10a +3.0.5 |checksum:8e06c84ed3a0159a0f2e6f7b10bbd056954ac2d33548318ccc3088556c8a8891 +3.0.6 |checksum:e6e0f30ff14b73b28f8e5803646aa6d2ed81b3b239e76815fd8160ea4121c650 +3.0.7 |checksum:7647c24bbebea0ef57a892953fc96349091924a399ee5d98f41da5d9db484816 +3.0.8 |checksum:6c8ff89feab635e332e115356451614a16d171485e34b6b30a6dea243caaaed7 +3.0.9 |checksum:b55c35100f5e1e191ff67eac8667aea9433d1492697c9434cdb35550cf6e4dd0 +3.0.10 |checksum:e168db8d4eccfc721685d939654698f1b419f018f45e38d17ab40033102987f4 +3.1.0 |checksum:3d9dd950ba0b235c4901d04e410c7e716feb491148bf0ca7daa0f510838b3bf7 +3.2.0 |checksum:5951f8d33ede2f68686c701142c6cc1004d6f525b0aa8e8279a1bf075542b0f0 +4.0.0 |checksum:36c185f8caecacb178500cabdc3e038862df640536c2e84ab763ae134462896f +4.0.1 |checksum:efd33857c0f41a413ec3ea20251f43c4826fe8a11b01099335f4a3b6777eb727 +4.1.0 |checksum:d26879b9a0675ea156c82e26971149349a1474aac3da4d0d2a04cc18e6df73a3 +4.1.1 |checksum:15df4e54e0fab19e225862b36dff823d5b87d57ea998f2e47c52ce01de82b3d9 +4.1.2 |checksum:7c2ed2d62d0305f2c33cba2e99b288df0f3f4343c367b8ee8cad8c735abc8568 +4.1.3 |checksum:d02f1465c7f012f77a61abdaa841a5273a2277247bc143bfa11cf139a29dbdf8 +4.1.4 |checksum:f7798b1682dcf750dab5f4f8da548fee36f30864a4e4b0d8a63295d159357c3b +4.1.5 |checksum:741c7dfe5e392ae39e22399546d25fe00ffdfc7a55e653e6a99b6770b1c0066c +4.2.0 |checksum:3cde88e3d440f63af3cd48edca88bd98872622403740ecda78b7d27161367486 +4.2.1 |checksum:dfc3eef6ef13c5750c3faab782c4db6c74a7bcc5d03e56e4edeec21aab034185 +4.3.0 |checksum:7accb37900d1599c6f0f40be92bc62a5db4e5a7eb64f33a858cf83e798dd1ddc +4.4.0 |checksum:71591ddbda99b5e12e4a46d377c87513850ca7aa4b1aa800ae02792cadee6be7 +4.4.1 |checksum:8a10333552216bf3d3846476cfe78dbc9b5724864e3f5016837724622d828f16 +4.4.2 |checksum:adba9d17de5957532223a1bf0e7bdba5ab849d6576e9210439a7d99e0cfa2595 +4.4.3 |checksum:b3eb96da1fe998f1c00665a9c645878518134cca7c35d39c4bb716e866f4cc57 +4.4.4 |checksum:43cab5a67ca409d62411f869ddb7a0a4de0988b489d3f1d610d9b6e521964fd5 +4.4.5 |checksum:af8713761f1b6604865830c54324e57c33e7cc05107ebdbee4e6d458f8f8fe7c +4.5.0 |checksum:54d94dc52cf98a51548c8f3e77031a3347508b542b8cb066100ed3ac40c03081 +4.5.1 |checksum:1456a9be103bf1de0d34ff6980b77a5a72cf3d4b35bbd2182ac62506981a234c +4.6.0 |checksum:e2498f90c75bd4c19d1739afadde8c03af26a881c8bd775f71d2f180de65b43f +4.6.1 |checksum:e2e96c2ee3ea81e1dc7b4b4abee23b6e552e669cfe456ee69126a29a03373cd7 +4.7.0 |checksum:963a1f5b21c95595fb3cf1e8531784bf3d8fe30302cf6f271b08aefdc63e453f +4.8.0 |checksum:5b9325f28b5be80ba8d43b7660f60ad67c9304fe8181dee89d3a348b13d2fada +4.9.0 |checksum:333619bc71d563ee60f26fc5f3a7f57bd89ee3191177fdce87a014dcb1b8d3b0 +4.10.0b1 |checksum:8791e2006a2ddbf8dc96cfc19633de01af8cb8687703177a85aeb3f959974d5b,rubygems:> 1.3.1 +4.10.0 |checksum:b67a289ae4a3968d93dab0803d0ef5a262b6f94138ab98072e489d2aa8af4034 +4.10.1 |checksum:63d2297712eb1d6219ab1cb9207d9a239ac9ad20463c0b58ca865f0b46deb5ec +4.11.0 |checksum:4c90ff17c492789fdd248369fa16ce65ef05576b3d9f593a49c6a0961dbcd5ee +4.12.0 |checksum:671110574e96377a03b328bfb7f6339540443eca0b62913bf8fe38e9ebcb4470 +4.12.1 |checksum:f87cd92457a343b4e951e1f1ac3e8183f98de4640a32f6ceb44628332d21a088 +4.13.0 |checksum:47e86c22a2d7810897e3eae9669ab9afa220f5e6cea5ac1d47164650a9b857d3 +4.14.0 |checksum:99a20cc5e7b901f6b493a8ca5e13439b73b19671eaaca68a00216c4f66765edc +4.14.1 |checksum:0fa8731445cf4a0c01570ec29aac4b50a0451ce66b1b31ad768f5035e3af7b90,ruby:~> 2.2 +4.15.0 |checksum:a5ec27d8055ad47444cfb7ce860bad8af2365772a82892f4a8a0d97e8e9e3b34,ruby:~> 2.2 +4.15.1 |checksum:9291a0f2247f50d15068ee6965b67cd7b678b0d273e18adf3c0b2ea4a890125c,ruby:< 3.1&>= 2.1 diff --git a/knife/spec/data/run_context/cookbooks/circular-dep1/attributes/default.rb b/knife/spec/data/run_context/cookbooks/circular-dep1/attributes/default.rb new file mode 100644 index 0000000000..e45e7d9f68 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/circular-dep1/attributes/default.rb @@ -0,0 +1,2 @@ +normal_unless[:attr_load_order] = [] +normal[:attr_load_order] << "circular-dep1::default" diff --git a/knife/spec/data/run_context/cookbooks/circular-dep1/definitions/circular_dep1_res.rb b/knife/spec/data/run_context/cookbooks/circular-dep1/definitions/circular_dep1_res.rb new file mode 100644 index 0000000000..fa1770b552 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/circular-dep1/definitions/circular_dep1_res.rb @@ -0,0 +1 @@ +LibraryLoadOrder.record('circular-dep1-definition') diff --git a/knife/spec/data/run_context/cookbooks/circular-dep1/libraries/lib.rb b/knife/spec/data/run_context/cookbooks/circular-dep1/libraries/lib.rb new file mode 100644 index 0000000000..b20b648789 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/circular-dep1/libraries/lib.rb @@ -0,0 +1,2 @@ +LibraryLoadOrder.record("circular-dep1") + diff --git a/knife/spec/data/run_context/cookbooks/circular-dep1/metadata.rb b/knife/spec/data/run_context/cookbooks/circular-dep1/metadata.rb new file mode 100644 index 0000000000..e990004b56 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/circular-dep1/metadata.rb @@ -0,0 +1,2 @@ +name "circular-dep1" +depends "circular-dep2" diff --git a/knife/spec/data/run_context/cookbooks/circular-dep1/providers/provider.rb b/knife/spec/data/run_context/cookbooks/circular-dep1/providers/provider.rb new file mode 100644 index 0000000000..ee66286da4 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/circular-dep1/providers/provider.rb @@ -0,0 +1 @@ +LibraryLoadOrder.record('circular-dep1-provider') diff --git a/knife/spec/data/run_context/cookbooks/circular-dep1/recipes/default.rb b/knife/spec/data/run_context/cookbooks/circular-dep1/recipes/default.rb new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/circular-dep1/recipes/default.rb diff --git a/knife/spec/data/run_context/cookbooks/circular-dep1/resources/resource.rb b/knife/spec/data/run_context/cookbooks/circular-dep1/resources/resource.rb new file mode 100644 index 0000000000..eaf68e9058 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/circular-dep1/resources/resource.rb @@ -0,0 +1,2 @@ +unified_mode true +LibraryLoadOrder.record('circular-dep1-resource') diff --git a/knife/spec/data/run_context/cookbooks/circular-dep2/attributes/default.rb b/knife/spec/data/run_context/cookbooks/circular-dep2/attributes/default.rb new file mode 100644 index 0000000000..37f396b1f9 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/circular-dep2/attributes/default.rb @@ -0,0 +1,2 @@ +normal_unless[:attr_load_order] = [] +normal[:attr_load_order] << "circular-dep2::default" diff --git a/knife/spec/data/run_context/cookbooks/circular-dep2/definitions/circular_dep2_res.rb b/knife/spec/data/run_context/cookbooks/circular-dep2/definitions/circular_dep2_res.rb new file mode 100644 index 0000000000..a23bf8f679 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/circular-dep2/definitions/circular_dep2_res.rb @@ -0,0 +1 @@ +LibraryLoadOrder.record('circular-dep2-definition') diff --git a/knife/spec/data/run_context/cookbooks/circular-dep2/libraries/lib.rb b/knife/spec/data/run_context/cookbooks/circular-dep2/libraries/lib.rb new file mode 100644 index 0000000000..ce0acc1231 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/circular-dep2/libraries/lib.rb @@ -0,0 +1,2 @@ +LibraryLoadOrder.record("circular-dep2") + diff --git a/knife/spec/data/run_context/cookbooks/circular-dep2/metadata.rb b/knife/spec/data/run_context/cookbooks/circular-dep2/metadata.rb new file mode 100644 index 0000000000..3df19e03de --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/circular-dep2/metadata.rb @@ -0,0 +1,2 @@ +name "circular-dep2" +depends "circular-dep1" diff --git a/knife/spec/data/run_context/cookbooks/circular-dep2/providers/provider.rb b/knife/spec/data/run_context/cookbooks/circular-dep2/providers/provider.rb new file mode 100644 index 0000000000..139ed59a9e --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/circular-dep2/providers/provider.rb @@ -0,0 +1 @@ +LibraryLoadOrder.record('circular-dep2-provider') diff --git a/knife/spec/data/run_context/cookbooks/circular-dep2/recipes/default.rb b/knife/spec/data/run_context/cookbooks/circular-dep2/recipes/default.rb new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/circular-dep2/recipes/default.rb diff --git a/knife/spec/data/run_context/cookbooks/circular-dep2/resources/resource.rb b/knife/spec/data/run_context/cookbooks/circular-dep2/resources/resource.rb new file mode 100644 index 0000000000..63eb08d5b5 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/circular-dep2/resources/resource.rb @@ -0,0 +1,2 @@ +unified_mode true +LibraryLoadOrder.record('circular-dep2-resource') diff --git a/knife/spec/data/run_context/cookbooks/dependency1/attributes/aa_first.rb b/knife/spec/data/run_context/cookbooks/dependency1/attributes/aa_first.rb new file mode 100644 index 0000000000..3059494198 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/dependency1/attributes/aa_first.rb @@ -0,0 +1,2 @@ +normal_unless[:attr_load_order] = [] +normal[:attr_load_order] << "dependency1::aa_first" diff --git a/knife/spec/data/run_context/cookbooks/dependency1/attributes/default.rb b/knife/spec/data/run_context/cookbooks/dependency1/attributes/default.rb new file mode 100644 index 0000000000..a65a3345bc --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/dependency1/attributes/default.rb @@ -0,0 +1,2 @@ +normal_unless[:attr_load_order] = [] +normal[:attr_load_order] << "dependency1::default" diff --git a/knife/spec/data/run_context/cookbooks/dependency1/attributes/unparsed_file b/knife/spec/data/run_context/cookbooks/dependency1/attributes/unparsed_file new file mode 100644 index 0000000000..60fee07cc6 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/dependency1/attributes/unparsed_file @@ -0,0 +1 @@ +raise "this should not be parsed by the loader" diff --git a/knife/spec/data/run_context/cookbooks/dependency1/attributes/zz_last.rb b/knife/spec/data/run_context/cookbooks/dependency1/attributes/zz_last.rb new file mode 100644 index 0000000000..94ffb30133 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/dependency1/attributes/zz_last.rb @@ -0,0 +1,2 @@ +normal_unless[:attr_load_order] = [] +normal[:attr_load_order] << "dependency1::zz_last" diff --git a/knife/spec/data/run_context/cookbooks/dependency1/definitions/dependency1_res.rb b/knife/spec/data/run_context/cookbooks/dependency1/definitions/dependency1_res.rb new file mode 100644 index 0000000000..4e4344e41f --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/dependency1/definitions/dependency1_res.rb @@ -0,0 +1 @@ +LibraryLoadOrder.record('dependency1-definition') diff --git a/knife/spec/data/run_context/cookbooks/dependency1/definitions/unparsed_file b/knife/spec/data/run_context/cookbooks/dependency1/definitions/unparsed_file new file mode 100644 index 0000000000..60fee07cc6 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/dependency1/definitions/unparsed_file @@ -0,0 +1 @@ +raise "this should not be parsed by the loader" diff --git a/knife/spec/data/run_context/cookbooks/dependency1/libraries/lib.rb b/knife/spec/data/run_context/cookbooks/dependency1/libraries/lib.rb new file mode 100644 index 0000000000..10dbb37a9e --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/dependency1/libraries/lib.rb @@ -0,0 +1,2 @@ +LibraryLoadOrder.record("dependency1") + diff --git a/knife/spec/data/run_context/cookbooks/dependency1/libraries/unparsed_file b/knife/spec/data/run_context/cookbooks/dependency1/libraries/unparsed_file new file mode 100644 index 0000000000..60fee07cc6 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/dependency1/libraries/unparsed_file @@ -0,0 +1 @@ +raise "this should not be parsed by the loader" diff --git a/knife/spec/data/run_context/cookbooks/dependency1/providers/provider.rb b/knife/spec/data/run_context/cookbooks/dependency1/providers/provider.rb new file mode 100644 index 0000000000..f0ca0004b6 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/dependency1/providers/provider.rb @@ -0,0 +1 @@ +LibraryLoadOrder.record('dependency1-provider') diff --git a/knife/spec/data/run_context/cookbooks/dependency1/providers/unparsed_file b/knife/spec/data/run_context/cookbooks/dependency1/providers/unparsed_file new file mode 100644 index 0000000000..60fee07cc6 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/dependency1/providers/unparsed_file @@ -0,0 +1 @@ +raise "this should not be parsed by the loader" diff --git a/knife/spec/data/run_context/cookbooks/dependency1/recipes/default.rb b/knife/spec/data/run_context/cookbooks/dependency1/recipes/default.rb new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/dependency1/recipes/default.rb diff --git a/knife/spec/data/run_context/cookbooks/dependency1/recipes/unparsed_file b/knife/spec/data/run_context/cookbooks/dependency1/recipes/unparsed_file new file mode 100644 index 0000000000..60fee07cc6 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/dependency1/recipes/unparsed_file @@ -0,0 +1 @@ +raise "this should not be parsed by the loader" diff --git a/knife/spec/data/run_context/cookbooks/dependency1/resources/resource.rb b/knife/spec/data/run_context/cookbooks/dependency1/resources/resource.rb new file mode 100644 index 0000000000..d355f7ce48 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/dependency1/resources/resource.rb @@ -0,0 +1,2 @@ +unified_mode true +LibraryLoadOrder.record('dependency1-resource') diff --git a/knife/spec/data/run_context/cookbooks/dependency1/resources/unparsed_file b/knife/spec/data/run_context/cookbooks/dependency1/resources/unparsed_file new file mode 100644 index 0000000000..60fee07cc6 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/dependency1/resources/unparsed_file @@ -0,0 +1 @@ +raise "this should not be parsed by the loader" diff --git a/knife/spec/data/run_context/cookbooks/dependency2/attributes/default.rb b/knife/spec/data/run_context/cookbooks/dependency2/attributes/default.rb new file mode 100644 index 0000000000..8917bf9730 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/dependency2/attributes/default.rb @@ -0,0 +1,2 @@ +normal_unless[:attr_load_order] = [] +normal[:attr_load_order] << "dependency2::default" diff --git a/knife/spec/data/run_context/cookbooks/dependency2/definitions/dependency2_res.rb b/knife/spec/data/run_context/cookbooks/dependency2/definitions/dependency2_res.rb new file mode 100644 index 0000000000..7839278319 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/dependency2/definitions/dependency2_res.rb @@ -0,0 +1 @@ +LibraryLoadOrder.record('dependency2-definition') diff --git a/knife/spec/data/run_context/cookbooks/dependency2/libraries/lib.rb b/knife/spec/data/run_context/cookbooks/dependency2/libraries/lib.rb new file mode 100644 index 0000000000..27b3be10d1 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/dependency2/libraries/lib.rb @@ -0,0 +1,2 @@ +LibraryLoadOrder.record("dependency2") + diff --git a/knife/spec/data/run_context/cookbooks/dependency2/providers/provider.rb b/knife/spec/data/run_context/cookbooks/dependency2/providers/provider.rb new file mode 100644 index 0000000000..6cc6310d54 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/dependency2/providers/provider.rb @@ -0,0 +1 @@ +LibraryLoadOrder.record('dependency2-provider') diff --git a/knife/spec/data/run_context/cookbooks/dependency2/recipes/default.rb b/knife/spec/data/run_context/cookbooks/dependency2/recipes/default.rb new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/dependency2/recipes/default.rb diff --git a/knife/spec/data/run_context/cookbooks/dependency2/resources/resource.rb b/knife/spec/data/run_context/cookbooks/dependency2/resources/resource.rb new file mode 100644 index 0000000000..5ec44d4564 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/dependency2/resources/resource.rb @@ -0,0 +1,2 @@ +unified_mode true +LibraryLoadOrder.record('dependency2-resource') diff --git a/knife/spec/data/run_context/cookbooks/include/recipes/default.rb b/knife/spec/data/run_context/cookbooks/include/recipes/default.rb new file mode 100644 index 0000000000..8d22994252 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/include/recipes/default.rb @@ -0,0 +1,24 @@ +module ::RanResources + def self.resources + @resources ||= [] + end +end +class RunContextCustomResource < Chef::Resource + action :create do + ruby_block '4' do + block { RanResources.resources << 4 } + end + recipe_eval do + ruby_block '1' do + block { RanResources.resources << 1 } + end + include_recipe 'include::includee' + ruby_block '3' do + block { RanResources.resources << 3 } + end + end + ruby_block '5' do + block { RanResources.resources << 5 } + end + end +end diff --git a/knife/spec/data/run_context/cookbooks/include/recipes/includee.rb b/knife/spec/data/run_context/cookbooks/include/recipes/includee.rb new file mode 100644 index 0000000000..87bb7f114e --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/include/recipes/includee.rb @@ -0,0 +1,3 @@ +ruby_block '2' do + block { RanResources.resources << 2 } +end diff --git a/knife/spec/data/run_context/cookbooks/no-default-attr/attributes/server.rb b/knife/spec/data/run_context/cookbooks/no-default-attr/attributes/server.rb new file mode 100644 index 0000000000..07294665b2 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/no-default-attr/attributes/server.rb @@ -0,0 +1,2 @@ +normal_unless[:attr_load_order] = [] +normal[:attr_load_order] << "no-default-attr::server" diff --git a/knife/spec/data/run_context/cookbooks/no-default-attr/definitions/no_default-attr_res.rb b/knife/spec/data/run_context/cookbooks/no-default-attr/definitions/no_default-attr_res.rb new file mode 100644 index 0000000000..cee4344a9c --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/no-default-attr/definitions/no_default-attr_res.rb @@ -0,0 +1 @@ +LibraryLoadOrder.record('no-default-attr-definition') diff --git a/knife/spec/data/run_context/cookbooks/no-default-attr/providers/provider.rb b/knife/spec/data/run_context/cookbooks/no-default-attr/providers/provider.rb new file mode 100644 index 0000000000..53b8adc934 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/no-default-attr/providers/provider.rb @@ -0,0 +1 @@ +LibraryLoadOrder.record('no-default-attr-provider') diff --git a/knife/spec/data/run_context/cookbooks/no-default-attr/recipes/default.rb b/knife/spec/data/run_context/cookbooks/no-default-attr/recipes/default.rb new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/no-default-attr/recipes/default.rb diff --git a/knife/spec/data/run_context/cookbooks/no-default-attr/resources/resource.rb b/knife/spec/data/run_context/cookbooks/no-default-attr/resources/resource.rb new file mode 100644 index 0000000000..fd8fa73b5a --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/no-default-attr/resources/resource.rb @@ -0,0 +1,2 @@ +unified_mode true +LibraryLoadOrder.record('no-default-attr-resource') diff --git a/knife/spec/data/run_context/cookbooks/test-with-circular-deps/attributes/default.rb b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/attributes/default.rb new file mode 100644 index 0000000000..77309462b1 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/attributes/default.rb @@ -0,0 +1,2 @@ +normal_unless[:attr_load_order] = [] +normal[:attr_load_order] << "test-with-circular-deps::default" diff --git a/knife/spec/data/run_context/cookbooks/test-with-circular-deps/definitions/test_with-circular-deps_res.rb b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/definitions/test_with-circular-deps_res.rb new file mode 100644 index 0000000000..f0840046b8 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/definitions/test_with-circular-deps_res.rb @@ -0,0 +1 @@ +LibraryLoadOrder.record('test-with-circular-deps-definition') diff --git a/knife/spec/data/run_context/cookbooks/test-with-circular-deps/libraries/lib.rb b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/libraries/lib.rb new file mode 100644 index 0000000000..76108f067c --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/libraries/lib.rb @@ -0,0 +1,2 @@ +LibraryLoadOrder.record("test-with-circular-deps") + diff --git a/knife/spec/data/run_context/cookbooks/test-with-circular-deps/metadata.rb b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/metadata.rb new file mode 100644 index 0000000000..280c5ae7ce --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/metadata.rb @@ -0,0 +1,2 @@ +name "test-with-circular-deps" +depends "circular-dep1" diff --git a/knife/spec/data/run_context/cookbooks/test-with-circular-deps/providers/provider.rb b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/providers/provider.rb new file mode 100644 index 0000000000..c25da707f2 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/providers/provider.rb @@ -0,0 +1 @@ +LibraryLoadOrder.record('test-with-circular-deps-provider') diff --git a/knife/spec/data/run_context/cookbooks/test-with-circular-deps/recipes/default.rb b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/recipes/default.rb new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/recipes/default.rb diff --git a/knife/spec/data/run_context/cookbooks/test-with-circular-deps/resources/resource.rb b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/resources/resource.rb new file mode 100644 index 0000000000..ffe7d099c9 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/resources/resource.rb @@ -0,0 +1,3 @@ +unified_mode true + +LibraryLoadOrder.record('test-with-circular-deps-resource') diff --git a/knife/spec/data/run_context/cookbooks/test-with-deps/attributes/default.rb b/knife/spec/data/run_context/cookbooks/test-with-deps/attributes/default.rb new file mode 100644 index 0000000000..c4cc8151a4 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/test-with-deps/attributes/default.rb @@ -0,0 +1,2 @@ +normal_unless[:attr_load_order] = [] +normal[:attr_load_order] << "test-with-deps::default" diff --git a/knife/spec/data/run_context/cookbooks/test-with-deps/definitions/test_with-deps_res.rb b/knife/spec/data/run_context/cookbooks/test-with-deps/definitions/test_with-deps_res.rb new file mode 100644 index 0000000000..c481734b54 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/test-with-deps/definitions/test_with-deps_res.rb @@ -0,0 +1 @@ +LibraryLoadOrder.record('test-with-deps-definition') diff --git a/knife/spec/data/run_context/cookbooks/test-with-deps/libraries/lib.rb b/knife/spec/data/run_context/cookbooks/test-with-deps/libraries/lib.rb new file mode 100644 index 0000000000..7dd942f8b3 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/test-with-deps/libraries/lib.rb @@ -0,0 +1 @@ +LibraryLoadOrder.record("test-with-deps") diff --git a/knife/spec/data/run_context/cookbooks/test-with-deps/metadata.rb b/knife/spec/data/run_context/cookbooks/test-with-deps/metadata.rb new file mode 100644 index 0000000000..8909f11630 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/test-with-deps/metadata.rb @@ -0,0 +1,3 @@ +name "test-with-deps" +depends "dependency1" +depends "dependency2" diff --git a/knife/spec/data/run_context/cookbooks/test-with-deps/providers/provider.rb b/knife/spec/data/run_context/cookbooks/test-with-deps/providers/provider.rb new file mode 100644 index 0000000000..96146c68b7 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/test-with-deps/providers/provider.rb @@ -0,0 +1 @@ +LibraryLoadOrder.record('test-with-deps-provider') diff --git a/knife/spec/data/run_context/cookbooks/test-with-deps/recipes/default.rb b/knife/spec/data/run_context/cookbooks/test-with-deps/recipes/default.rb new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/test-with-deps/recipes/default.rb diff --git a/knife/spec/data/run_context/cookbooks/test-with-deps/recipes/server.rb b/knife/spec/data/run_context/cookbooks/test-with-deps/recipes/server.rb new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/test-with-deps/recipes/server.rb diff --git a/knife/spec/data/run_context/cookbooks/test-with-deps/resources/resource.rb b/knife/spec/data/run_context/cookbooks/test-with-deps/resources/resource.rb new file mode 100644 index 0000000000..203efabec5 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/test-with-deps/resources/resource.rb @@ -0,0 +1,2 @@ +unified_mode true +LibraryLoadOrder.record('test-with-deps-resource') diff --git a/knife/spec/data/run_context/cookbooks/test/attributes/default.rb b/knife/spec/data/run_context/cookbooks/test/attributes/default.rb new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/test/attributes/default.rb diff --git a/knife/spec/data/run_context/cookbooks/test/attributes/george.rb b/knife/spec/data/run_context/cookbooks/test/attributes/george.rb new file mode 100644 index 0000000000..8ea4454c79 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/test/attributes/george.rb @@ -0,0 +1 @@ +default[:george] = "washington" diff --git a/knife/spec/data/run_context/cookbooks/test/definitions/new_animals.rb b/knife/spec/data/run_context/cookbooks/test/definitions/new_animals.rb new file mode 100644 index 0000000000..5b00553cfe --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/test/definitions/new_animals.rb @@ -0,0 +1,9 @@ +define :new_dog, :is_cute => true do + dog "#{params[:name]}" do + cute params[:is_cute] + end +end + +define :new_badger do + badger "#{params[:name]}" +end diff --git a/knife/spec/data/run_context/cookbooks/test/definitions/new_cat.rb b/knife/spec/data/run_context/cookbooks/test/definitions/new_cat.rb new file mode 100644 index 0000000000..a49b53348c --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/test/definitions/new_cat.rb @@ -0,0 +1,5 @@ +define :new_cat, :is_pretty => true do + cat "#{params[:name]}" do + pretty_kitty params[:is_pretty] + end +end diff --git a/knife/spec/data/run_context/cookbooks/test/definitions/test_res.rb b/knife/spec/data/run_context/cookbooks/test/definitions/test_res.rb new file mode 100644 index 0000000000..b6a2e53488 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/test/definitions/test_res.rb @@ -0,0 +1 @@ +LibraryLoadOrder.record('test-definition') diff --git a/knife/spec/data/run_context/cookbooks/test/providers/provider.rb b/knife/spec/data/run_context/cookbooks/test/providers/provider.rb new file mode 100644 index 0000000000..abf0bd24e9 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/test/providers/provider.rb @@ -0,0 +1 @@ +LibraryLoadOrder.record('test-provider') diff --git a/knife/spec/data/run_context/cookbooks/test/recipes/default.rb b/knife/spec/data/run_context/cookbooks/test/recipes/default.rb new file mode 100644 index 0000000000..d769dc826d --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/test/recipes/default.rb @@ -0,0 +1,5 @@ + +cat "einstein" do + pretty_kitty true +end + diff --git a/knife/spec/data/run_context/cookbooks/test/recipes/one.rb b/knife/spec/data/run_context/cookbooks/test/recipes/one.rb new file mode 100644 index 0000000000..7795cc1d4a --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/test/recipes/one.rb @@ -0,0 +1,7 @@ +cat "loulou" do + pretty_kitty true +end + +new_cat "birthday" do + pretty_kitty false +end diff --git a/knife/spec/data/run_context/cookbooks/test/recipes/two.rb b/knife/spec/data/run_context/cookbooks/test/recipes/two.rb new file mode 100644 index 0000000000..01def1b2ac --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/test/recipes/two.rb @@ -0,0 +1,7 @@ +cat "peanut" do + pretty_kitty true +end + +new_cat "fat peanut" do + pretty_kitty false +end diff --git a/knife/spec/data/run_context/cookbooks/test/resources/resource.rb b/knife/spec/data/run_context/cookbooks/test/resources/resource.rb new file mode 100644 index 0000000000..54c99d2fb8 --- /dev/null +++ b/knife/spec/data/run_context/cookbooks/test/resources/resource.rb @@ -0,0 +1,3 @@ +unified_mode true + +LibraryLoadOrder.record('test-resource') diff --git a/knife/spec/data/run_context/nodes/run_context.rb b/knife/spec/data/run_context/nodes/run_context.rb new file mode 100644 index 0000000000..076d21ab89 --- /dev/null +++ b/knife/spec/data/run_context/nodes/run_context.rb @@ -0,0 +1,5 @@ +## +# Nodes should have a unique name +## +name "compile" +run_list "test", "test::one", "test::two" diff --git a/knife/spec/data/sample_msu1.xml b/knife/spec/data/sample_msu1.xml new file mode 100644 index 0000000000..cc68dbf060 --- /dev/null +++ b/knife/spec/data/sample_msu1.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<unattend xmlns="urn:schemas-microsoft-com:unattend"> + <servicing> + <package action="install"> + <assemblyIdentity name="Package_for_KB2859903" version="10.2.1.0" language="neutral" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35"/> + <source location="%configsetroot%\IE10-Windows6.1-KB2859903-x86.CAB" /> + </package> + </servicing> +</unattend> + diff --git a/knife/spec/data/sample_msu2.xml b/knife/spec/data/sample_msu2.xml new file mode 100644 index 0000000000..6f95e04f93 --- /dev/null +++ b/knife/spec/data/sample_msu2.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<unattend xmlns="urn:schemas-microsoft-com:unattend"> + <servicing> + <package action="install"> + <assemblyIdentity name="Package_for_KB2859903" version="10.2.1.0" language="neutral" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35"/> + <source location="%configsetroot%\IE10-Windows6.1-KB2859903-x86.CAB" /> + </package> + <package action="install"> + <assemblyIdentity name="Package_for_abc" version="10.2.1.0" language="neutral" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35"/> + <source location="%configsetroot%\abc.CAB" /> + </package> + </servicing> +</unattend> + diff --git a/knife/spec/data/sample_msu3.xml b/knife/spec/data/sample_msu3.xml new file mode 100644 index 0000000000..0ef09da206 --- /dev/null +++ b/knife/spec/data/sample_msu3.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<unattend xmlns="urn:schemas-microsoft-com:unattend"> + <servicing> + <package action="install"> + <assemblyIdentity name="Package_for_KB2859903" version="10.2.1.0" language="neutral" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35"/> + <source location="%configsetroot%\IE10-Windows6.1-KB2859903-x86.CAB" /> + </package> + </servicing> + <servicing> + <package action="install"> + <assemblyIdentity name="Package_for_abc" version="10.2.1.0" language="neutral" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35"/> + <source location="%configsetroot%\abc.CAB" /> + </package> + </servicing> +</unattend> + diff --git a/knife/spec/data/search_queries_to_transform.txt b/knife/spec/data/search_queries_to_transform.txt new file mode 100644 index 0000000000..4a05d5405e --- /dev/null +++ b/knife/spec/data/search_queries_to_transform.txt @@ -0,0 +1,98 @@ +afield:[* TO *] +content:afield__=__* + +afield:[a TO *] +content:[afield__=__a TO afield__=__\ufff0] + +afield:[* TO b] +content:[afield__=__ TO afield__=__b] + +*:* +*:* + +role:mon +content:role__=__mon + +role:mon AND role:prod +content:role__=__mon AND content:role__=__prod + +run_list:role\[rubberband\] AND run_list:role\[whale\] +content:run_list__=__role\[rubberband\] AND content:run_list__=__role\[whale\] + +sharable_server:[* TO *] +content:sharable_server__=__* + +run_list:role\[nfs_server\] AND sharable_server:[* TO *] +content:run_list__=__role\[nfs_server\] AND content:sharable_server__=__* + +run_list:role\[nfs_server\] AND sharable_server:[* TO *] +content:run_list__=__role\[nfs_server\] AND content:sharable_server__=__* + +(role:prod AND x_y:true) +(content:role__=__prod AND content:x_y__=__true) + +hostname:[* TO *] AND role:prod +content:hostname__=__* AND content:role__=__prod + +role:t_mem AND role:prod NOT hostname:ip-1-2-3-4 +content:role__=__t_mem AND content:role__=__prod NOT content:hostname__=__ip-1-2-3-4 + +ohai_time:[1234.567 TO *] +content:[ohai_time__=__1234.567 TO ohai_time__=__\ufff0] + +ohai_time:{1234.567 TO *} +content:{ohai_time__=__1234.567 TO ohai_time__=__\ufff0} + +ohai_time:[* TO baz] +content:[ohai_time__=__ TO ohai_time__=__baz] + +ohai_time:{* TO baz} +content:{ohai_time__=__ TO ohai_time__=__baz} + +tags:apples*.for.eating.com +content:tags__=__apples*.for.eating.com + +role:safe AND ohai_time:[1234.567 TO *] AND whiz_bang:x5 +content:role__=__safe AND content:[ohai_time__=__1234.567 TO ohai_time__=__\ufff0] AND content:whiz_bang__=__x5 + +role:safe AND ohai_time:[* TO 1234.567] AND whiz_bang:x5 +content:role__=__safe AND content:[ohai_time__=__ TO ohai_time__=__1234.567] AND content:whiz_bang__=__x5 + +animal:[ape TO zebra] +content:[animal__=__ape TO animal__=__zebra] + +animal:{ape TO zebra} +content:{animal__=__ape TO animal__=__zebra} + +((value:[1 TO 3] OR nested_b1_a2_a3:B1_A2_A3-c) OR value:[5 TO *]) +((content:[value__=__1 TO value__=__3] OR content:nested_b1_a2_a3__=__B1_A2_A3-c) OR content:[value__=__5 TO value__=__\ufff0]) + +((value:{1 TO 3} OR value:{1 TO 3}) OR run_list:recipe\[alpha\]) +((content:{value__=__1 TO value__=__3} OR content:{value__=__1 TO value__=__3}) OR content:run_list__=__recipe\[alpha\]) + +words:"one two three" +content:"words__=__one two three" + +words:"one \"two\" three" +content:"words__=__one \"two\" three" + +words:"\"one two\" three" +content:"words__=__\"one two\" three" + +words:"one two \"three\"" +content:"words__=__one two \"three\"" + +words:"one two \"three\"" OR words:"\"one two\" three" AND words:"one \"two\" three" +content:"words__=__one two \"three\"" OR content:"words__=__\"one two\" three" AND content:"words__=__one \"two\" three" + +words:\"* +content:words__=__\"* + +-version:0.9.12 +-content:version__=__0.9.12 + +!version:0.9.12 +NOT content:version__=__0.9.12 + +ec2:* +content:ec2__=__* diff --git a/knife/spec/data/shef-config.rb b/knife/spec/data/shef-config.rb new file mode 100644 index 0000000000..1ace5efd21 --- /dev/null +++ b/knife/spec/data/shef-config.rb @@ -0,0 +1,11 @@ +ohai[:disabled_plugins] << "darwin::system_profiler" << "darwin::kernel" << "darwin::ssh_host_key" << "network_listeners" +ohai[:disabled_plugins] << "virtualization" << "darwin::virtualization" +ohai[:disabled_plugins] << "darwin::uptime" << "darwin::filesystem" << "dmi" << "languages" << "perl" << "python" << "java" +ohai[:disabled_plugins] << "linux::block_device" << "linux::kernel" << "linux::ssh_host_key" << "linux::virtualization" +ohai[:disabled_plugins] << "linux::cpu" << "linux::memory" << "ec2" << "rackspace" << "eucalyptus" << "ip_scopes" +ohai[:disabled_plugins] << "solaris2::cpu" << "solaris2::dmi" << "solaris2::filesystem" << "solaris2::kernel" +ohai[:disabled_plugins] << "solaris2::virtualization" << "solaris2::zpools" +ohai[:disabled_plugins] << "c" << "php" << "mono" << "groovy" << "lua" << "erlang" +ohai[:disabled_plugins] << "kernel" << "linux::filesystem" << "ruby" +chef_repo_path __dir__ +cookbook_path "#{chef_repo_path}/cookbooks" diff --git a/knife/spec/data/snap_package/async_result_success.json b/knife/spec/data/snap_package/async_result_success.json new file mode 100644 index 0000000000..09781ad5bd --- /dev/null +++ b/knife/spec/data/snap_package/async_result_success.json @@ -0,0 +1,6 @@ +{ + "type": "async", + "status-code": 202, + "status": "Accepted", + "change": "401" +} diff --git a/knife/spec/data/snap_package/change_id_result.json b/knife/spec/data/snap_package/change_id_result.json new file mode 100644 index 0000000000..cd823d7cbc --- /dev/null +++ b/knife/spec/data/snap_package/change_id_result.json @@ -0,0 +1,175 @@ +{ + "type": "sync", + "status-code": 200, + "status": "OK", + "result": { + "id": "15", + "kind": "install-snap", + "summary": "Install snap \"hello\"", + "status": "Done", + "tasks": [{ + "id": "165", + "kind": "prerequisites", + "summary": "Ensure prerequisites for \"hello\" are available", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.22104314Z", + "ready-time": "2018-09-22T20:25:25.231090966Z" + }, { + "id": "166", + "kind": "download-snap", + "summary": "Download snap \"hello\" (20) from channel \"stable\"", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.221070859Z", + "ready-time": "2018-09-22T20:25:25.24321909Z" + }, { + "id": "167", + "kind": "validate-snap", + "summary": "Fetch and check assertions for snap \"hello\" (20)", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.221080163Z", + "ready-time": "2018-09-22T20:25:25.55308904Z" + }, { + "id": "168", + "kind": "mount-snap", + "summary": "Mount snap \"hello\" (20)", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.221082984Z", + "ready-time": "2018-09-22T20:25:25.782452658Z" + }, { + "id": "169", + "kind": "copy-snap-data", + "summary": "Copy snap \"hello\" data", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.221085677Z", + "ready-time": "2018-09-22T20:25:25.790911883Z" + }, { + "id": "170", + "kind": "setup-profiles", + "summary": "Setup snap \"hello\" (20) security profiles", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.221088261Z", + "ready-time": "2018-09-22T20:25:25.972796111Z" + }, { + "id": "171", + "kind": "link-snap", + "summary": "Make snap \"hello\" (20) available to the system", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.221090669Z", + "ready-time": "2018-09-22T20:25:25.986931331Z" + }, { + "id": "172", + "kind": "auto-connect", + "summary": "Automatically connect eligible plugs and slots of snap \"hello\"", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.221093357Z", + "ready-time": "2018-09-22T20:25:25.996914144Z" + }, { + "id": "173", + "kind": "set-auto-aliases", + "summary": "Set automatic aliases for snap \"hello\"", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.221097651Z", + "ready-time": "2018-09-22T20:25:26.009155888Z" + }, { + "id": "174", + "kind": "setup-aliases", + "summary": "Setup snap \"hello\" aliases", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.221100379Z", + "ready-time": "2018-09-22T20:25:26.021062388Z" + }, { + "id": "175", + "kind": "run-hook", + "summary": "Run install hook of \"hello\" snap if present", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.221103116Z", + "ready-time": "2018-09-22T20:25:26.031383884Z" + }, { + "id": "176", + "kind": "start-snap-services", + "summary": "Start snap \"hello\" (20) services", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.221110251Z", + "ready-time": "2018-09-22T20:25:26.039564637Z" + }, { + "id": "177", + "kind": "run-hook", + "summary": "Run configure hook of \"hello\" snap if present", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.221115952Z", + "ready-time": "2018-09-22T20:25:26.05069451Z" + } + ], + "ready": true, + "spawn-time": "2018-09-22T20:25:25.221130149Z", + "ready-time": "2018-09-22T20:25:26.050696298Z", + "data": { + "snap-names": ["hello"] + } + } +} diff --git a/knife/spec/data/snap_package/find_result_failure.json b/knife/spec/data/snap_package/find_result_failure.json new file mode 100644 index 0000000000..ec0d82a3b8 --- /dev/null +++ b/knife/spec/data/snap_package/find_result_failure.json @@ -0,0 +1,10 @@ +{ + "type": "error", + "status-code": 404, + "status": "Not Found", + "result": { + "message": "snap not found", + "kind": "snap-not-found", + "value": "hello2" + } +} diff --git a/knife/spec/data/snap_package/find_result_success.json b/knife/spec/data/snap_package/find_result_success.json new file mode 100644 index 0000000000..f19f24dcef --- /dev/null +++ b/knife/spec/data/snap_package/find_result_success.json @@ -0,0 +1,70 @@ +{ + "type": "sync", + "status-code": 200, + "status": "OK", + "result": [{ + "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6", + "title": "hello", + "summary": "GNU Hello, the \"hello world\" snap", + "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/", + "download-size": 65536, + "name": "hello", + "publisher": { + "id": "canonical", + "username": "canonical", + "display-name": "Canonical", + "validation": "verified" + }, + "developer": "canonical", + "status": "available", + "type": "app", + "version": "2.10", + "channel": "stable", + "ignore-validation": false, + "revision": "20", + "confinement": "strict", + "private": false, + "devmode": false, + "jailmode": false, + "contact": "mailto:snaps@canonical.com", + "license": "GPL-3.0", + "channels": { + "latest/beta": { + "revision": "29", + "confinement": "strict", + "version": "2.10.1", + "channel": "beta", + "epoch": "0", + "size": 65536 + }, + "latest/candidate": { + "revision": "20", + "confinement": "strict", + "version": "2.10", + "channel": "candidate", + "epoch": "0", + "size": 65536 + }, + "latest/edge": { + "revision": "34", + "confinement": "strict", + "version": "2.10.42", + "channel": "edge", + "epoch": "0", + "size": 65536 + }, + "latest/stable": { + "revision": "20", + "confinement": "strict", + "version": "2.10", + "channel": "stable", + "epoch": "0", + "size": 65536 + } + }, + "tracks": ["latest"] + } + ], + "sources": ["store"], + "suggested-currency": "USD" +} diff --git a/knife/spec/data/snap_package/get_by_name_result_failure.json b/knife/spec/data/snap_package/get_by_name_result_failure.json new file mode 100644 index 0000000000..c8c1bb7342 --- /dev/null +++ b/knife/spec/data/snap_package/get_by_name_result_failure.json @@ -0,0 +1,10 @@ +{ + "type": "error", + "status-code": 404, + "status": "Not Found", + "result": { + "message": "snap not installed", + "kind": "snap-not-found", + "value": "aws-cliasdfasdf" + } +} diff --git a/knife/spec/data/snap_package/get_by_name_result_success.json b/knife/spec/data/snap_package/get_by_name_result_success.json new file mode 100644 index 0000000000..05517362ab --- /dev/null +++ b/knife/spec/data/snap_package/get_by_name_result_success.json @@ -0,0 +1,38 @@ +{ + "type": "sync", + "status-code": 200, + "status": "OK", + "result": { + "id": "CRrJViJiSuDcCkU31G0xpNRVNaj4P960", + "summary": "Universal Command Line Interface for Amazon Web Services", + "description": "This package provides a unified command line interface to Amazon Web\nServices.\n", + "installed-size": 15851520, + "name": "aws-cli", + "publisher": { + "id": "S7iQ7mKDXBDliQqRcgefvc2TKXIH9pYk", + "username": "aws", + "display-name": "Amazon Web Services", + "validation": "verified" + }, + "developer": "aws", + "status": "active", + "type": "app", + "version": "1.15.71", + "channel": "", + "tracking-channel": "stable", + "ignore-validation": false, + "revision": "135", + "confinement": "classic", + "private": false, + "devmode": false, + "jailmode": false, + "apps": [{ + "snap": "aws-cli", + "name": "aws" + } + ], + "contact": "", + "mounted-from": "/var/lib/snapd/snaps/aws-cli_135.snap", + "install-date": "2018-09-17T20:39:38.516Z" + } +} diff --git a/knife/spec/data/snap_package/get_conf_success.json b/knife/spec/data/snap_package/get_conf_success.json new file mode 100644 index 0000000000..e83ffbfbe3 --- /dev/null +++ b/knife/spec/data/snap_package/get_conf_success.json @@ -0,0 +1,10 @@ +{ + "type": "sync", + "status-code": 200, + "status": "OK", + "result": { + "address": "0.0.0.0", + "allow-privileged": true, + "anonymous-auth": false + } +} diff --git a/knife/spec/data/snap_package/result_failure.json b/knife/spec/data/snap_package/result_failure.json new file mode 100644 index 0000000000..e65120ad33 --- /dev/null +++ b/knife/spec/data/snap_package/result_failure.json @@ -0,0 +1,9 @@ +{ + "type": "error", + "status-code": 401, + "status": "Unauthorized", + "result": { + "message": "access denied", + "kind": "login-required" + } +} diff --git a/knife/spec/data/ssl/5e707473.0 b/knife/spec/data/ssl/5e707473.0 new file mode 100644 index 0000000000..5c5cf87e11 --- /dev/null +++ b/knife/spec/data/ssl/5e707473.0 @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC6DCCAlGgAwIBAgIJANlevg7kzqvpMA0GCSqGSIb3DQEBBQUAMFcxITAfBgNV +BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEeMBwGA1UECxMVU25ha2VvaWwg +Q2VydGlmaWNhdGVzMRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMDkxMjE5MTkxODUy +WhcNMTAwMTE4MTkxODUyWjBXMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0 +eSBMdGQxHjAcBgNVBAsTFVNuYWtlb2lsIENlcnRpZmljYXRlczESMBAGA1UEAxMJ +bG9jYWxob3N0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDhm9En1DL3aC4H +j5/SA6FXm6B/0AoVzoPfWX2rpkRcz/XX24JEhQhLXStjhDr4p/IrARnZ8shy0MA4 +wNpNPEn5c0RvqKypHzX+AeQkBx8J1/8vnMAoM9b/4pd0FqgRW1UbhvqQDzkWmVyK +Tz5yCiTntxDzudAtHlTo8V6E7UEDkwIDAQABo4G7MIG4MB0GA1UdDgQWBBTmAcyA +CqQblJ1L4sOIzmkdIAtY6jCBiAYDVR0jBIGAMH6AFOYBzIAKpBuUnUviw4jOaR0g +C1jqoVukWTBXMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxHjAc +BgNVBAsTFVNuYWtlb2lsIENlcnRpZmljYXRlczESMBAGA1UEAxMJbG9jYWxob3N0 +ggkA2V6+DuTOq+kwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQBe4f9R +s0g5GCFekabzl9AHvIn4ITxenvuyaNX9f2BJbdgoD03wlGycBxjbC57RjFVfetu7 +mtUYuJSx7iojBSC+LzotGptrG9d2BxrWOKBfF2K+dyoIG8kZL5aLfS0be6Cc5O3c +L/IPadJhBu/EfyGI2vL1l8GspXdOxaFzHprpgA== +-----END CERTIFICATE----- diff --git a/knife/spec/data/ssl/binary/chef-rspec-der.cert b/knife/spec/data/ssl/binary/chef-rspec-der.cert Binary files differnew file mode 100644 index 0000000000..e49df6252a --- /dev/null +++ b/knife/spec/data/ssl/binary/chef-rspec-der.cert diff --git a/knife/spec/data/ssl/binary/chef-rspec-der.key b/knife/spec/data/ssl/binary/chef-rspec-der.key Binary files differnew file mode 100644 index 0000000000..d8adadc5c9 --- /dev/null +++ b/knife/spec/data/ssl/binary/chef-rspec-der.key diff --git a/knife/spec/data/ssl/chef-rspec.cert b/knife/spec/data/ssl/chef-rspec.cert new file mode 100644 index 0000000000..9215a39362 --- /dev/null +++ b/knife/spec/data/ssl/chef-rspec.cert @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEkjCCA3qgAwIBAgIJAOEDC5RFoEUZMA0GCSqGSIb3DQEBCwUAMIGMMQswCQYD +VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHU2VhdHRsZTEN +MAsGA1UEChMEQ2hlZjETMBEGA1UECxMKZGV2ZWxvcGVyczESMBAGA1UEAxMJa2Fs +bGlzdGVjMR4wHAYJKoZIhvcNAQkBFg9kYW5Ab3BzY29kZS5jb20wHhcNMjAwODEy +MTEyOTUxWhcNMzAwODEwMTEyOTUxWjCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +Cldhc2hpbmd0b24xEDAOBgNVBAcTB1NlYXR0bGUxDTALBgNVBAoTBENoZWYxEzAR +BgNVBAsTCmRldmVsb3BlcnMxEjAQBgNVBAMTCWthbGxpc3RlYzEeMBwGCSqGSIb3 +DQEJARYPZGFuQG9wc2NvZGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAw5l9EtBHsJrb5AIxARP695an3v+509gOXRKnjWIRnU+knbdTnEdjlGGG +SxuFR7Fnp2OM8ed7iPIKSrcM0vQ+g7vYKCv5Z8UR3sbLY8UHm9AgZ/bLAHEHS2if +1WHPD5DOe1B7HwW0IfEiW4/WakkVn4uoWw5rCZ87f4YCrETomXIo1n/rMFHf+yoY +guuEfGQxRcQdlEZM9YMlMByQvXlVR5IVhpiMHBCyV6KzxjZVCgTlvS8nPMiiHpoO +pgB6BGEQ/nn4Kapk40baPqpT4EP/DnBnbhhR3kBQ6MQRlh7bl5vjH5xFSFwGUUA9 +IcaDTwfliD27bo36aMvcBhrsmbSwqwIDAQABo4H0MIHxMAwGA1UdEwQFMAMBAf8w +HQYDVR0OBBYEFLzxnG28b4VO7VT5UUDjt9/PBK1uMIHBBgNVHSMEgbkwgbaAFLzx +nG28b4VO7VT5UUDjt9/PBK1uoYGSpIGPMIGMMQswCQYDVQQGEwJVUzETMBEGA1UE +CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHU2VhdHRsZTENMAsGA1UEChMEQ2hlZjET +MBEGA1UECxMKZGV2ZWxvcGVyczESMBAGA1UEAxMJa2FsbGlzdGVjMR4wHAYJKoZI +hvcNAQkBFg9kYW5Ab3BzY29kZS5jb22CCQDhAwuURaBFGTANBgkqhkiG9w0BAQsF +AAOCAQEAoTCRzUbZOPrJdpd928fQPt/HsYODDmgWQJIPucdLKmlY5wb3zSc1B5H6 +zBUmSFylnDLKhZlO+gojBuQDhr2h9bMXn8RvE0A/Dqq9XcNsblMn0HjoJmjv/x8Q +wFfrwIWj1wHoGHromkJaedWCRGlVW1oLZa99JmQCNee4bjcwkK2H0xRqX8STbqJV +z+uEBf5fDc4EioULwfxa6B15XDD09k14uHtlV6JwTmahDjpdKV/ICKBi/WN0aQg1 +9k7OAkW5cnzmS6uFFjrvWuNy4ey4j1c4U5GogxEgCsattshHNO+icWRCN2gPg2Nx +SKEXNcUA4jRWGF7PRgY/oyjULObFqw== +-----END CERTIFICATE----- diff --git a/knife/spec/data/ssl/chef-rspec.key b/knife/spec/data/ssl/chef-rspec.key new file mode 100644 index 0000000000..29aaecf2a6 --- /dev/null +++ b/knife/spec/data/ssl/chef-rspec.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAw5l9EtBHsJrb5AIxARP695an3v+509gOXRKnjWIRnU+knbdT +nEdjlGGGSxuFR7Fnp2OM8ed7iPIKSrcM0vQ+g7vYKCv5Z8UR3sbLY8UHm9AgZ/bL +AHEHS2if1WHPD5DOe1B7HwW0IfEiW4/WakkVn4uoWw5rCZ87f4YCrETomXIo1n/r +MFHf+yoYguuEfGQxRcQdlEZM9YMlMByQvXlVR5IVhpiMHBCyV6KzxjZVCgTlvS8n +PMiiHpoOpgB6BGEQ/nn4Kapk40baPqpT4EP/DnBnbhhR3kBQ6MQRlh7bl5vjH5xF +SFwGUUA9IcaDTwfliD27bo36aMvcBhrsmbSwqwIDAQABAoIBAQC+hddKaA4se+sL +4QaSoj+mwtypXjZHnv/+sJj8IjY+IMGbzmJmqzLX6VbB+gCMoMTySwmS54NxFTHp +LPwUz0vFTUdzecHpzg9mDAU5HUYYA1ZNbhq2R2JvlW16j1b9NnOpse77fLbFCPgK +b8TOqnmheot2hkjEipGN2Z7o5gYaz1/3PtolkP1ypCTG6Bh7V3ohBLBIEdjA552o +HNGe3t6PpvoNtBqaeb/j/SAOvg+8DGF1WQtE+5Y1koSlhABYWkHzHC1fHAzRMSHH +ZMfKOQNusRgBRNJabdVqkuTbvyRCQEb2YGQxPPYV2C+AxAlh3APeYTg90vUqAq/3 +ivNdilcBAoGBAOLELc0mcTftDbIMWVnrzAGAJOCMz3FkwGcV8nqNeA3R77e3pWL2 +5+bKadWQGjjpR3ZEYt/RxHsoGCW3NtM44icxqVCTPW/unp2xqadjuvcsKrxk+1wD +OdvVrwcd/N+KzgXO+Hm7xbV/loFms3ueGfCRbOueQyP4dj9MyOBGlO2hAoGBANzQ +u8IrZBG0DL8YFdmjw4YWUENIOtABPU1qHo/sugTQjI9K3/E3LA7aaGnl2P//1tao +SR/aP/To90H6D989/JomhkEKKA+DyL1sRL1NMdtWwrKdEq32W8fUN0JEA+Q1FMsd +Hk6Ix+KrZVg9cTb9HoGikDxeHW3pPKDWaEkWIQLLAoGAD13N4L3/JBQLPop5r487 +9soRNao1EHEMXK/vC4D0prMYNHHcYjVrB4el3lPygvLD5e7CaHpVfyb7Y+rjazLK +mG9UEuK3YhNgaj00yuQGMmOqzbNmGRka3ZvATZIppZhJV7lruwwPXLo1n7Uu6myP +Q28HW3wQ/qoCkU2JuzDtPKECgYBUrYcTEuixEUbCEU5vw6k7RltJMe27zn3frg5C +Sxmatw7v9Fqkee/fUkowMgBhS47rimVgXaWhGaWYG3jytyajRpq9XlO2f2b/nQFP +RscTwdWwASQkqhDQNMVsGAEWBnUO3v+8Rh/BANFAYW+FEtQcCmcdf0nx2DtzwkUD +ogTOuQKBgCbEg+/ND/p8xKwY9LtjLKnrQSL5tSH/7prhLJvVVdW7FMRfKSp1t2xc +kfJFqO1Lcf2j7hiclval3xDoWUretNQ5379T0Ob30WuIomSfeqcxJjCUtyN3fUqr +z/QG9dk/23OOYJhRgAmttBDqpk5uB5mOQgSftdELNyw0EOyNIBfZ +-----END RSA PRIVATE KEY----- diff --git a/knife/spec/data/ssl/key.pem b/knife/spec/data/ssl/key.pem new file mode 100644 index 0000000000..50b8fd83c8 --- /dev/null +++ b/knife/spec/data/ssl/key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQDhm9En1DL3aC4Hj5/SA6FXm6B/0AoVzoPfWX2rpkRcz/XX24JE +hQhLXStjhDr4p/IrARnZ8shy0MA4wNpNPEn5c0RvqKypHzX+AeQkBx8J1/8vnMAo +M9b/4pd0FqgRW1UbhvqQDzkWmVyKTz5yCiTntxDzudAtHlTo8V6E7UEDkwIDAQAB +AoGAGicC1tgdVFqqQ0wd3a14DXzH3SkTkjWPSdvI2pX6hLvCptQWRLUbIglZ1z5j +y6FETEHjakVfgRe7wJhyddOQS3eeVt/aK0xBHz/JiJuIF+NzbJT9t01nPV21abYU +lWIhWV8Ja39a5LKV6hee0TTYdAub7BVQ95kwrqMqRcDoXHECQQDxpAgq925Cmlz1 +0Q1WZq2A/o8oqPvPS1FulPK2OgyOyQSK+DdcK2xUKGWMn0m9fDLLzj/pe/H3dN1I +b8Z/iiWrAkEA7wPlesZX3GzfqQLd6GYGBa4IdrV5dHdeoCCVRnkFr06KjcqpAhg1 +7i0T9frSC5EfRCfbGNgo4eutT9+D7HJhuQJAZeDBrNPbQetxDBbSp73sovkwhHUS +jah0scnMtvWse7rW1nymYo7QQn8xqWMzJNerVvAjVB50ut8juLmfmAA3twJAQy9/ +NBHI5Mcd365Unlz/WF1hN60vZNOhH7XJADRIqsyTGeRbuaEAl+DH+Z71qBa1CT2C +0usAIvFSmF8mADLu0QJAHSSh6zLNInvkhDjYAmEu3oeFQgQ4Rp7oiMaBZ6VVuOMo +4GU9CA18iI75NaO7FOfquJPkIJ0li0xadVofUpaJcg== +-----END RSA PRIVATE KEY----- diff --git a/knife/spec/data/ssl/private_key.pem b/knife/spec/data/ssl/private_key.pem new file mode 100644 index 0000000000..b6636604a8 --- /dev/null +++ b/knife/spec/data/ssl/private_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA49TA0y81ps0zxkOpmf5V4/c4IeR5yVyQFpX3JpxO4TquwnRh +8VSUhrw8kkTLmB3cS39Db+3HadvhoqCEbqPE6915kXSuk/cWIcNozujLK7tkuPEy +YVsyTioQAddSdfe+8EhQVf3oHxaKmUd6waXrWqYCnhxgOjxocenREYNhZ/OETIei +PbOku47vB4nJK/0GhKBytL2XnsRgfKgDxf42BqAi1jglIdeq8lAWZNF9TbNBU21A +O1iuT7Pm6LyQujhggPznR5FJhXKRUARXBJZawxpGV4dGtdcahwXNE4601aXPra+x +PcRd2puCNoEDBzgVuTSsLYeKBDMSfs173W1QYwIDAQABAoIBAGF05q7vqOGbMaSD +2Q7YbuE/JTHKTBZIlBI1QC2x+0P5GDxyEFttNMOVzcs7xmNhkpRw8eX1LrInrpMk +WsIBKAFFEfWYlf0RWtRChJjNl+szE9jQxB5FJnWtJH/FHa78tR6PsF24aQyzVcJP +g0FGujBihwgfV0JSCNOBkz8MliQihjQA2i8PGGmo4R4RVzGfxYKTIq9vvRq/+QEa +Q4lpVLoBqnENpnY/9PTl6JMMjW2b0spbLjOPVwDaIzXJ0dChjNXo15K5SHI5mALJ +I5gN7ODGb8PKUf4619ez194FXq+eob5YJdilTFKensIUvt3YhP1ilGMM+Chi5Vi/ +/RCTw3ECgYEA9jTw4wv9pCswZ9wbzTaBj9yZS3YXspGg26y6Ohq3ZmvHz4jlT6uR +xK+DDcUiK4072gci8S4Np0fIVS7q6ivqcOdzXPrTF5/j+MufS32UrBbUTPiM1yoO +ECcy+1szl/KoLEV09bghPbvC58PFSXV71evkaTETYnA/F6RK12lEepcCgYEA7OSy +bsMrGDVU/MKJtwqyGP9ubA53BorM4Pp9VVVSCrGGVhb9G/XNsjO5wJC8J30QAo4A +s59ZzCpyNRy046AB8jwRQuSwEQbejSdeNgQGXhZ7aIVUtuDeFFdaIz/zjVgxsfj4 +DPOuzieMmJ2MLR4F71ocboxNoDI7xruPSE8dDhUCgYA3vx732cQxgtHwAkeNPJUz +dLiE/JU7CnxIoSB9fYUfPLI+THnXgzp7NV5QJN2qzMzLfigsQcg3oyo6F2h7Yzwv +GkjlualIRRzCPaCw4Btkp7qkPvbs1QngIHALt8fD1N69P3DPHkTwjG4COjKWgnJq +qoHKS6Fe/ZlbigikI6KsuwKBgQCTlSLoyGRHr6oj0hqz01EDK9ciMJzMkZp0Kvn8 +OKxlBxYW+jlzut4MQBdgNYtS2qInxUoAnaz2+hauqhSzntK3k955GznpUatCqx0R +b857vWviwPX2/P6+E3GPdl8IVsKXCvGWOBZWTuNTjQtwbDzsUepWoMgXnlQJSn5I +YSlLxQKBgQD16Gw9kajpKlzsPa6XoQeGmZALT6aKWJQlrKtUQIrsIWM0Z6eFtX12 +2jjHZ0awuCQ4ldqwl8IfRogWMBkHOXjTPVK0YKWWlxMpD/5+bGPARa5fir8O1Zpo +Y6S6MeZ69Rp89ma4ttMZ+kwi1+XyHqC/dlcVRW42Zl5Dc7BALRlJjQ== +-----END RSA PRIVATE KEY----- diff --git a/knife/spec/data/ssl/private_key_with_whitespace.pem b/knife/spec/data/ssl/private_key_with_whitespace.pem new file mode 100644 index 0000000000..b393a3f13d --- /dev/null +++ b/knife/spec/data/ssl/private_key_with_whitespace.pem @@ -0,0 +1,32 @@ + + +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA49TA0y81ps0zxkOpmf5V4/c4IeR5yVyQFpX3JpxO4TquwnRh +8VSUhrw8kkTLmB3cS39Db+3HadvhoqCEbqPE6915kXSuk/cWIcNozujLK7tkuPEy +YVsyTioQAddSdfe+8EhQVf3oHxaKmUd6waXrWqYCnhxgOjxocenREYNhZ/OETIei +PbOku47vB4nJK/0GhKBytL2XnsRgfKgDxf42BqAi1jglIdeq8lAWZNF9TbNBU21A +O1iuT7Pm6LyQujhggPznR5FJhXKRUARXBJZawxpGV4dGtdcahwXNE4601aXPra+x +PcRd2puCNoEDBzgVuTSsLYeKBDMSfs173W1QYwIDAQABAoIBAGF05q7vqOGbMaSD +2Q7YbuE/JTHKTBZIlBI1QC2x+0P5GDxyEFttNMOVzcs7xmNhkpRw8eX1LrInrpMk +WsIBKAFFEfWYlf0RWtRChJjNl+szE9jQxB5FJnWtJH/FHa78tR6PsF24aQyzVcJP +g0FGujBihwgfV0JSCNOBkz8MliQihjQA2i8PGGmo4R4RVzGfxYKTIq9vvRq/+QEa +Q4lpVLoBqnENpnY/9PTl6JMMjW2b0spbLjOPVwDaIzXJ0dChjNXo15K5SHI5mALJ +I5gN7ODGb8PKUf4619ez194FXq+eob5YJdilTFKensIUvt3YhP1ilGMM+Chi5Vi/ +/RCTw3ECgYEA9jTw4wv9pCswZ9wbzTaBj9yZS3YXspGg26y6Ohq3ZmvHz4jlT6uR +xK+DDcUiK4072gci8S4Np0fIVS7q6ivqcOdzXPrTF5/j+MufS32UrBbUTPiM1yoO +ECcy+1szl/KoLEV09bghPbvC58PFSXV71evkaTETYnA/F6RK12lEepcCgYEA7OSy +bsMrGDVU/MKJtwqyGP9ubA53BorM4Pp9VVVSCrGGVhb9G/XNsjO5wJC8J30QAo4A +s59ZzCpyNRy046AB8jwRQuSwEQbejSdeNgQGXhZ7aIVUtuDeFFdaIz/zjVgxsfj4 +DPOuzieMmJ2MLR4F71ocboxNoDI7xruPSE8dDhUCgYA3vx732cQxgtHwAkeNPJUz +dLiE/JU7CnxIoSB9fYUfPLI+THnXgzp7NV5QJN2qzMzLfigsQcg3oyo6F2h7Yzwv +GkjlualIRRzCPaCw4Btkp7qkPvbs1QngIHALt8fD1N69P3DPHkTwjG4COjKWgnJq +qoHKS6Fe/ZlbigikI6KsuwKBgQCTlSLoyGRHr6oj0hqz01EDK9ciMJzMkZp0Kvn8 +OKxlBxYW+jlzut4MQBdgNYtS2qInxUoAnaz2+hauqhSzntK3k955GznpUatCqx0R +b857vWviwPX2/P6+E3GPdl8IVsKXCvGWOBZWTuNTjQtwbDzsUepWoMgXnlQJSn5I +YSlLxQKBgQD16Gw9kajpKlzsPa6XoQeGmZALT6aKWJQlrKtUQIrsIWM0Z6eFtX12 +2jjHZ0awuCQ4ldqwl8IfRogWMBkHOXjTPVK0YKWWlxMpD/5+bGPARa5fir8O1Zpo +Y6S6MeZ69Rp89ma4ttMZ+kwi1+XyHqC/dlcVRW42Zl5Dc7BALRlJjQ== +-----END RSA PRIVATE KEY----- + + + diff --git a/knife/spec/data/standalone_cookbook/Gemfile b/knife/spec/data/standalone_cookbook/Gemfile new file mode 100644 index 0000000000..9c961848d8 --- /dev/null +++ b/knife/spec/data/standalone_cookbook/Gemfile @@ -0,0 +1 @@ +source "https://rubygems.org/"
\ No newline at end of file diff --git a/knife/spec/data/standalone_cookbook/chefignore b/knife/spec/data/standalone_cookbook/chefignore new file mode 100644 index 0000000000..cd18e699c1 --- /dev/null +++ b/knife/spec/data/standalone_cookbook/chefignore @@ -0,0 +1,9 @@ +# +# The ignore file allows you to skip files in cookbooks with the same name that appear +# later in the search path. +# + +recipes/ignoreme.rb + # comments can be indented +ignored +vendor/bundle/* diff --git a/knife/spec/data/standalone_cookbook/recipes/default.rb b/knife/spec/data/standalone_cookbook/recipes/default.rb new file mode 100644 index 0000000000..c2fa53be32 --- /dev/null +++ b/knife/spec/data/standalone_cookbook/recipes/default.rb @@ -0,0 +1,3 @@ +# +# Nothing ot see here +#
\ No newline at end of file diff --git a/knife/spec/data/standalone_cookbook/vendor/bundle/ruby/2.0.0/gems/multi_json-1.9.0/lib/multi_json.rb b/knife/spec/data/standalone_cookbook/vendor/bundle/ruby/2.0.0/gems/multi_json-1.9.0/lib/multi_json.rb new file mode 100644 index 0000000000..3b992add1a --- /dev/null +++ b/knife/spec/data/standalone_cookbook/vendor/bundle/ruby/2.0.0/gems/multi_json-1.9.0/lib/multi_json.rb @@ -0,0 +1 @@ +# This is a dummy ruby file
\ No newline at end of file diff --git a/knife/spec/data/templates/chef-seattle20160930-4388-1crv7ef.txt b/knife/spec/data/templates/chef-seattle20160930-4388-1crv7ef.txt new file mode 100644 index 0000000000..f476ccd704 --- /dev/null +++ b/knife/spec/data/templates/chef-seattle20160930-4388-1crv7ef.txt @@ -0,0 +1 @@ +Do do do do, do do do do, do do do do, do do do do
\ No newline at end of file diff --git a/knife/spec/data/templates/chef-seattle20160930-4388-jjfoae.txt b/knife/spec/data/templates/chef-seattle20160930-4388-jjfoae.txt new file mode 100644 index 0000000000..f476ccd704 --- /dev/null +++ b/knife/spec/data/templates/chef-seattle20160930-4388-jjfoae.txt @@ -0,0 +1 @@ +Do do do do, do do do do, do do do do, do do do do
\ No newline at end of file diff --git a/knife/spec/data/templates/chef-seattle20160930-4388-umeq2c.txt b/knife/spec/data/templates/chef-seattle20160930-4388-umeq2c.txt new file mode 100644 index 0000000000..f476ccd704 --- /dev/null +++ b/knife/spec/data/templates/chef-seattle20160930-4388-umeq2c.txt @@ -0,0 +1 @@ +Do do do do, do do do do, do do do do, do do do do
\ No newline at end of file diff --git a/knife/spec/data/templates/failed.erb b/knife/spec/data/templates/failed.erb new file mode 100644 index 0000000000..e077ac8684 --- /dev/null +++ b/knife/spec/data/templates/failed.erb @@ -0,0 +1,5 @@ +This is a template + +Which includes some content + +And will fail <%= nil[] %> diff --git a/knife/spec/data/templates/seattle.txt b/knife/spec/data/templates/seattle.txt new file mode 100644 index 0000000000..19f6290939 --- /dev/null +++ b/knife/spec/data/templates/seattle.txt @@ -0,0 +1 @@ +Seattle is a great town. Next time you visit, you should buy me a beer.
\ No newline at end of file diff --git a/knife/spec/data/trusted_certs/example.crt b/knife/spec/data/trusted_certs/example.crt new file mode 100644 index 0000000000..832aebe2ec --- /dev/null +++ b/knife/spec/data/trusted_certs/example.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDkjCCAnoCCQDihI8kxGYTFTANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMC +VVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMRAwDgYDVQQKEwdZb3VD +b3JwMRMwEQYDVQQLEwpPcGVyYXRpb25zMRYwFAYDVQQDEw1leGFtcGxlLmxvY2Fs +MR0wGwYJKoZIhvcNAQkBFg5tZUBleGFtcGxlLmNvbTAeFw0xMzEwMTcxODAxMzVa +Fw0yMzEwMTUxODAxMzVaMIGKMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAO +BgNVBAcTB1NlYXR0bGUxEDAOBgNVBAoTB1lvdUNvcnAxEzARBgNVBAsTCk9wZXJh +dGlvbnMxFjAUBgNVBAMTDWV4YW1wbGUubG9jYWwxHTAbBgkqhkiG9w0BCQEWDm1l +QGV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyKBo +U+Bdni0xZK/NCzdLdi2X+TyW5eahbYMx+r1GDcVqCICvrthBCVLVFsQ8rvOHwTPi +AxQJGxb9TLSXRgXQSlH6FLjIUceuOtpan3qYVJ1v7AxY4DgNvYBpbtJz5MQedJnT +g2F+rXzkwaD6CWBqWHeGU0oP3r7bq1AMD6XEsK2w2/zHtG7TEnL45ARv1PsyrU5M +ZAW/XyoMyq1k2Lpv7YR5kAvTq1+4RSt/it2RFE7R0AVbaQ0MeAnllfySiHHHlaOT +FVd/qPSiGISxsUmmzA3Z08+0sfJwkrnJXbLscCBYndd7gMGgtczGjJtul0Ch3GFa +/Pn5McjwF272+usJ1wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQCzPePWifWNECsG +nL8on1AtFMkczE1/pdRS4YUl/Tc926MpezptSja8rL31+4Bom37/wYPG7HygtAQl +R4FHpAtuqJKPOfjUmDNsIXRFnytrnflTpctDu/Nbj4PDCy01k/sTDUQt+s+lEBL8 +M8ArmfLZ8PCfAwnXmJQ5rggDFKqegjt6z1RsSglbMiASE7+KkpBnzaqH6fET6IQz +WgAjv6WdRfwgfJjOTSX4XMpCSet9KaWmXExKrxiVng2Uu6E+ShVAyKaGMuc1B7VA +oxnnVaVapFv5lOWucQr4KkC7EgaUZnyt8duOc8+Yvd+y3Xd2dcHUnmegRxly4jRV +/lXbFAUb +-----END CERTIFICATE----- diff --git a/knife/spec/data/trusted_certs/example_no_cn.crt b/knife/spec/data/trusted_certs/example_no_cn.crt new file mode 100644 index 0000000000..6b42b40d99 --- /dev/null +++ b/knife/spec/data/trusted_certs/example_no_cn.crt @@ -0,0 +1,36 @@ +-----BEGIN CERTIFICATE----- +MIIGPzCCBCegAwIBAgIJAKwtLqBeqNzfMA0GCSqGSIb3DQEBBQUAMHIxCzAJBgNV +BAYTAlVTMQswCQYDVQQIEwJXQTEQMA4GA1UEBxMHU2VhdHRsZTEQMA4GA1UEChMH +WW91Q29ycDETMBEGA1UECxMKT3BlcmF0aW9uczEdMBsGCSqGSIb3DQEJARYObWVA +ZXhhbXBsZS5jb20wHhcNMTYxMDMxMTkxMzQ2WhcNMjYxMDI5MTkxMzQ2WjByMQsw +CQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxEDAOBgNV +BAoTB1lvdUNvcnAxEzARBgNVBAsTCk9wZXJhdGlvbnMxHTAbBgkqhkiG9w0BCQEW +Dm1lQGV4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +s1OiWnMV3shxVccqzenDBww5rSou9Ab/VqujKisJ54dXyHukYMxh9MJwlRDsy0FB +uKRAyNfhM43hSMYhtF7NS//D1lI/LDvIQkBaH8R834bvK102Avmsx7zKPOo/CUkd +g7uuL2eRzRszEuAREH1E7/PpTj11CjirG9i7FlbKj7vDA1Nqvtb0kHdiQuH2Cojy +Uf1uVFyE5UliFXtePDrxpOAfJUbcSdOLsK8olKHGCb0cfN/tCfbyEY8rHGsAUK2A +afuHRTR7pRQwfqJ5EK3DBbbFz+GSi+9zWFOudfqTsczS/HtpMbF8HBwqBAa+mpU/ +UjmhpTYQ+rgVtWtEcttboeK6jvFBFLyQ6VRcrDi/8lmAnm1Q+RZk5g3GwZMhIMNU +5XQZf6jsUsIFBuOaRyLn9dXkgyO7gOy8n8Yw+YdIFt29kaqZ6pu9kpS0jquxzSKj +MVS4OrThLwgazfQu/BlOvJpQfcNPI/VP9c41yHKpeoIh6oxNDc/212/wwgwPgD83 +8YXddupaSuE++h9Z10CCZgwux8deTlMjzETIMiIo8R3KV0pJgZ11akeJ8USr+QQ2 ++fO/GdpNUa5nNTgF3t4zTF3DPToqj6KDgxLhUdXopF1hLYgwr8FKOtn9KXP+I0hz +hWzZoX9gwFLEPrUy265Gpw8TVTmNuSiiZtgJDSDKTBcCAwEAAaOB1zCB1DAdBgNV +HQ4EFgQUr5Y6dxhyVxfhwFsEKLDIXxQ2zBswgaQGA1UdIwSBnDCBmYAUr5Y6dxhy +VxfhwFsEKLDIXxQ2zBuhdqR0MHIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJXQTEQ +MA4GA1UEBxMHU2VhdHRsZTEQMA4GA1UEChMHWW91Q29ycDETMBEGA1UECxMKT3Bl +cmF0aW9uczEdMBsGCSqGSIb3DQEJARYObWVAZXhhbXBsZS5jb22CCQCsLS6gXqjc +3zAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQBYXgqXAnocH6i8o47c +BZPMGO9y4LCB4YNIrZPKRNFvRl2aolA5KiBxr6WJp1iczxVA4lCmXU1LGfvRPHec +nHtVax3+Q1JCZhBSv/txQTjgn72qoJyCsPmjyWifbE1jFdRj0g74/Eu/0ku3L0vV +jTlqzJXQIzRkQm+Y5OrZo92tXaOgO+C0qdd6gaEaIIya6bzrBpW95NtVymhXi2Qf +7G7Z/yw8XhoQiDJaPHF6XavC3dYvi51cehnPR4E6Jok23kbJEe3Qw5Yh747JjSsS +Sz07CKqTFcFjHI2f1sFdDjw34lj5mtOf3pEpRGGmvzkF8zm/sVQQ2rCKnqEe7zPy +Bg9guzVpORG+g76hGFZcYnz78LLNrIYcuYoLcbbZh404wjmifVKO5OC1dRgmJTuc +VnJe92568Y9cUAjrLztxp5gwXgYUllsXweJ2UGiHxSBqUfCCGG5vK5sYs52HR6wJ +wRSvwk/VHifYPxJ54RRB51ebYjmD1j41tRseHdFq21qpXSvr9DFLUJBvdN9zA/6t +xCBlXAdYxD0n0/bruUYNoXBeMhLp+WKSAQvTlVIyqoNQCo1OBBzBVNg9otl3jw5d +1QOhodRqmS5UQAJptuXtk8WN8OZqMCCeogIfdpa5tJG+/fxFML9EvqedS4c05Wf/ +oYdVLVWSjyoA2l4Xb4LdexAgCg== +-----END CERTIFICATE----- diff --git a/knife/spec/data/trusted_certs/intermediate.pem b/knife/spec/data/trusted_certs/intermediate.pem new file mode 100644 index 0000000000..78148b0fcf --- /dev/null +++ b/knife/spec/data/trusted_certs/intermediate.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEjzCCA3egAwIBAgIQBp4dt3/PHfupevXlyaJANzANBgkqhkiG9w0BAQUFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaMEgxCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxIjAgBgNVBAMTGURpZ2lDZXJ0IFNlY3Vy +ZSBTZXJ2ZXIgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7V+Qh +qdWbYDd+jqFhf4HiGsJ1ZNmRUAvkNkQkbjDSm3on+sJqrmpwCTi5IArIZRBKiKwx +8tyS8mOhXYBjWYCSIxzm73ZKUDXJ2HE4ue3w5kKu0zgmeTD5IpTG26Y/QXiQ2N5c +fml9+JAVOtChoL76srIZodgr0c6/a91Jq6OS/rWryME+7gEA2KlEuEJziMNh9atK +gygK0tRJ+mqxzd9XLJTl4sqDX7e6YlwvaKXwwLn9K9HpH9gaYhW9/z2m98vv5ttl +LyU47PvmIGZYljQZ0hXOIdMkzNkUb9j+Vcfnb7YPGoxJvinyulqagSY3JG/XSBJs +Lln1nBi72fZo4t9FAgMBAAGjggFaMIIBVjASBgNVHRMBAf8ECDAGAQH/AgEAMA4G +A1UdDwEB/wQEAwIBhjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6 +Ly9vY3NwLmRpZ2ljZXJ0LmNvbTB7BgNVHR8EdDByMDegNaAzhjFodHRwOi8vY3Js +My5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxSb290Q0EuY3JsMDegNaAzhjFo +dHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxSb290Q0EuY3Js +MD0GA1UdIAQ2MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5k +aWdpY2VydC5jb20vQ1BTMB0GA1UdDgQWBBSQcds363PI79zVHhK2NLorWqCmkjAf +BgNVHSMEGDAWgBQD3lA1VtFMu2bwo+IbG8OXsj3RVTANBgkqhkiG9w0BAQUFAAOC +AQEAMM7RlVEArgYLoQ4CwBestn+PIPZAdXQczHixpE/q9NDEnaLegQcmH0CIUfAf +z7dMQJnQ9DxxmHOIlywZ126Ej6QfnFog41FcsMWemWpPyGn3EP9OrRnZyVizM64M +2ZYpnnGycGOjtpkWQh1l8/egHn3F1GUUsmKE1GxcCAzYbJMrtHZZitF//wPYwl24 +LyLWOPD2nGt9RuuZdPfrSg6ppgTre87wXGuYMVqYQOtpxAX0IKjKCDplbDgV9Vws +slXkLGtB8L5cRspKKaBIXiDSRf8F3jSvcEuBOeLKB1d8tjHcISnivpcOd5AUUUDh +v+PMGxmcJcqnBrJT3yOyzxIZow== +-----END CERTIFICATE----- diff --git a/knife/spec/data/trusted_certs/opscode.pem b/knife/spec/data/trusted_certs/opscode.pem new file mode 100644 index 0000000000..e421a4e6e9 --- /dev/null +++ b/knife/spec/data/trusted_certs/opscode.pem @@ -0,0 +1,57 @@ +-----BEGIN CERTIFICATE----- +MIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg +U2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83 +nf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd +KpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f +/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX +kujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0 +/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C +AQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY +aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6 +Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1 +oDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD +QS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v +d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh +xtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB +CwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl +5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA +8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC +2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit +c+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0 +j6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFDTCCA/WgAwIBAgIQBZ8R1sZP2Lbc8x554UUQ2DANBgkqhkiG9w0BAQsFADBN +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E +aWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMTQxMTEwMDAwMDAwWhcN +MTcxMTE0MTIwMDAwWjBlMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv +bjEQMA4GA1UEBxMHU2VhdHRsZTEbMBkGA1UEChMSQ2hlZiBTb2Z0d2FyZSwgSW5j +MRIwEAYDVQQDDAkqLmNoZWYuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC3xCIczkV10O5jTDpbd4YlPLC6kfnVoOkno2N/OOlcLQu3ulj/Lj1j4r6e +2XthJLcFgTO+y+1/IKnnpLKDfkx1YngWEBXEBP+MrrpDUKKs053s45/bI9QBPISA +tXgnYxMH9Glo6FWWd13TUq++OKGw1p1wazH64XK4MAf5y/lkmWXIWumNuO35ZqtB +ME3wJISwVHzHB2CQjlDklt+Mb0APEiIFIZflgu9JNBYzLdvUtxiz15FUZQI7SsYL +TfXOD1KBNMWqN8snG2e5gRAzB2D161DFvAZt8OiYUe+3QurNlTYVzeHv1ok6UqgM +ZcLzg8m801rRip0D7FCGvMCU/ktdAgMBAAGjggHPMIIByzAfBgNVHSMEGDAWgBQP +gGEcgjFh1S8o541GOLQs4cbZ4jAdBgNVHQ4EFgQUwldjw4Pb4HV+wxGZ7MSSRh+d +pm4wHQYDVR0RBBYwFIIJKi5jaGVmLmlvggdjaGVmLmlvMA4GA1UdDwEB/wQEAwIF +oDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwawYDVR0fBGQwYjAvoC2g +K4YpaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NzY2Etc2hhMi1nMy5jcmwwL6At +oCuGKWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zc2NhLXNoYTItZzMuY3JsMEIG +A1UdIAQ7MDkwNwYJYIZIAYb9bAEBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3 +LmRpZ2ljZXJ0LmNvbS9DUFMwfAYIKwYBBQUHAQEEcDBuMCQGCCsGAQUFBzABhhho +dHRwOi8vb2NzcC5kaWdpY2VydC5jb20wRgYIKwYBBQUHMAKGOmh0dHA6Ly9jYWNl +cnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJTZWN1cmVTZXJ2ZXJDQS5jcnQw +DAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAvcTWenNuvvrhX2omm8LQ +zWOuu8jqpoflACwD4lOSZ4TgOe4pQGCjXq8aRBD5k+goqQrPVf9lHnelUHFQac0Q +5WT4YUmisUbF0S4uY5OGQymM52MvUWG4ODL4gaWhFvN+HAXrDPP/9iitsjV0QOnl +CDq7Q4/XYRYW3opu5nLLbfW6v4QvF5yzZagEACGs7Vt32p6l391UcU8f6wiB3uMD +eioCvjpv/+2YOUNlDPCM3uBubjUhHOwO817wBxXkzdk1OSRe4jzcw/uX6wL7birt +fbaSkpilvVX529pSzB2Lvi9xWOoGMM578dpQ0h3PwhmmvKhhCWP+pI05k3oSkYCP +ng== +-----END CERTIFICATE----- diff --git a/knife/spec/data/trusted_certs/root.pem b/knife/spec/data/trusted_certs/root.pem new file mode 100644 index 0000000000..fd4341df26 --- /dev/null +++ b/knife/spec/data/trusted_certs/root.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB +CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 +nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt +43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P +T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 +gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR +TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw +DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr +hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg +06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF +PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls +YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- diff --git a/knife/spec/data/windows_certificates/base64_test.cer b/knife/spec/data/windows_certificates/base64_test.cer new file mode 100644 index 0000000000..763216f86a --- /dev/null +++ b/knife/spec/data/windows_certificates/base64_test.cer @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgIQPAc5ZRAOLL1PCvdo8CoWDTANBgkqhkiG9w0BAQsFADAh +MR8wHQYDVQQDDBZ3d3cuZHVtbXljaGVmdGVzdHMuY29tMCAXDTIwMDMwNjEyMDUz +MVoYDzI4OTkxMjMxMTgzMDAwWjAhMR8wHQYDVQQDDBZ3d3cuZHVtbXljaGVmdGVz +dHMuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6uZ0V5zobMQm +JPtZxt4vYtL/As7U6sUBVe9oR9OCYvyIDmpuPcNnrJ26L+iu2W5Kd+840Dv6tHS4 +yOV07bYBU+nVHiCdEn/K7Q5ITv/8uXv39dvlSuSrIn4P+I2vhSQjIy/B94QPD/xE +dD0WDym1ySY2zQsL4T+yKoaXc5tiBoWBwAdl6/RiXeOm2kBXhIDcW4MLlB0BXtDJ +l7syB30mOvNsQT6UlymI1q7fpsaPBTo8V3lUWooVVmQciiYquoD34gq7XpdGQOLJ +V7aSIch1BoQyeQJfWsKzv/R5yzAzw+2zeRf301USunBXwhoac/Sx4xrJxjRknGTs +7tsCNQUmRQIDAQABo3MwcTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYB +BQUHAwIGCCsGAQUFBwMBMCEGA1UdEQQaMBiCFnd3dy5kdW1teWNoZWZ0ZXN0cy5j +b20wHQYDVR0OBBYEFGQa7l1ZPNbhj0s64g1/nyY+xULdMA0GCSqGSIb3DQEBCwUA +A4IBAQCS3chRs1LUvlq7Hj1kx3CtAhjTE75eEWz8wzWZ+DGppGnMUQg0vwrw7JPd +s3ODAFor62J97Fmb1sQ9/lSGan0CwBtCMqzHr3hoKbpVR9aFKu/Kt21zE4pEvFgZ +NVrxOFofmZ072VRdRpRK3RcnV58I02Xyb+5VR8lTbHpIsUOj+i9+y5ZuuOXoRDpI +G+AdIAfvcBbshPkI62gSFvBUdic0fcMVPZ5rFWaDjW2XFXZ6s/e5mPHNjpGpSZy7 +2y9ku9kB6ywBQXx9U21DBoIDxfprSylQGxtUuXaeCwnRvpT0Ifto5/KaeH4IzJQq +ZYGdPzBO7WBpk/AsO6buw3kQ9M5h +-----END CERTIFICATE----- diff --git a/knife/spec/data/windows_certificates/othertest.cer b/knife/spec/data/windows_certificates/othertest.cer new file mode 100644 index 0000000000..f4ff69eb08 --- /dev/null +++ b/knife/spec/data/windows_certificates/othertest.cer @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDVjCCAj6gAwIBAgIQEavqVx6t6J9MQFWBGNLMAzANBgkqhkiG9w0BAQsFADAo +MSYwJAYDVQQDDB13d3cuZHVtbXljaGVmdGVzdHNhbm90aGVyLmNvbTAgFw0yMDAz +MDYxMDA2NDVaGA8yODk5MTIzMTE4MzAwMFowKDEmMCQGA1UEAwwdd3d3LmR1bW15 +Y2hlZnRlc3RzYW5vdGhlci5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQDwvGtD46qB4T9Fyt0oEXX2C0zgMQuFPWSutAKWArQCXjDlSD2+a4DvFHvz +czv8P5P9XZM52cYDIk1nsdG75ZsgzLoiFvk2MtGpH3A1J8tXxJAROZCj8mAwoVpQ +e791otvHH6LYK4iABHUN3PvuQEfbLTqcPPDFB3jgqV7mkI3wiGxZ5txrjBJo4f8Q +9ZcqhR33zhp5+eBUH46T7ZY524/CI2dv+1a58LX9y1neqe6Bg0K51Rw0O8Zm/8kD +31paD75qkq1qWS0BD0OOhVXXzsO1C4OH8L53nAtrwGceTmNRKPgMD5+Rwe6zaw94 +G7m+UeMyEkFJ87Y8+HeSU7F09TcVAgMBAAGjejB4MA4GA1UdDwEB/wQEAwIFoDAd +BgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwKAYDVR0RBCEwH4Idd3d3LmR1 +bW15Y2hlZnRlc3RzYW5vdGhlci5jb20wHQYDVR0OBBYEFLzhpImP5MmDZ0s60syY +agCrc9NoMA0GCSqGSIb3DQEBCwUAA4IBAQArFhqQQ0U3yzLr47Bcu5WwooULCarW +caroDqJySdQV+abAMf0aqr8TTl+o9dVyl7Lzt+G4WQgl8Ay7P5dlEv3mGRl7J2PF +o55PPPmZQkORzwv772d7Nzv9bQemMcYZF38su9+k3mcMh0vxdHoaz39TpUQt5Tz1 +3WC56dflaCbRMq8/fQhYivxfByq3BOf4ghW+BswZMjrV9wMDwKv21ebYAULmxaU3 +/Z0igXN3O4V4RKavONUwRMyRspLFlxm0EEirP+FruQS+/ABIPxuaReYLSqnD9Jqa +03UdwrqC3bymfKI1zYYToSAqBO8qjim8+cjvdEaXGILW7YBKXNEvyJ20 +-----END CERTIFICATE----- diff --git a/knife/spec/data/windows_certificates/test.cer b/knife/spec/data/windows_certificates/test.cer new file mode 100644 index 0000000000..1c358b5035 --- /dev/null +++ b/knife/spec/data/windows_certificates/test.cer @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgIQX3zqNCJbsKlEvzCz3Z9aNDANBgkqhkiG9w0BAQsFADAh +MR8wHQYDVQQDDBZ3d3cuZHVtbXljaGVmdGVzdHMuY29tMCAXDTIwMDMwNTEwMjcw +NVoYDzIxMjAwMzA1MTAzNzA2WjAhMR8wHQYDVQQDDBZ3d3cuZHVtbXljaGVmdGVz +dHMuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtuYKDb6woWIH +HPPOrcVpgJFVxbkjgk+tsYwbIiqR9jtRaKE6nM/awOgn9/dFF4k8KB8Em0sUx7Vq +J3YhK2N2cAacgP2Frqqf5znpNBBOg968RoZzGx0EiXFvLsqC4y8ggApWTbMXPRk4 +1a7GlpUpSqI3y5cLeEbzwGQKu8I1I+v7P2fTlnJPHarM7sBbL8bieukkFHYu78iV +u1wpKOCCfs5DTmJu8WN+z1Mar9vyrWMBlt2wBBgNHPz5mcXUzJHTzaI/D9RGgBgF +V0IkNqISx/IzR62jjj2g6MgTH4G/0mM6O5sxduM4yGmWZNZpVzh0yMLgH619MZlj +SMQIN3U/SQIDAQABo3MwcTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYB +BQUHAwIGCCsGAQUFBwMBMCEGA1UdEQQaMBiCFnd3dy5kdW1teWNoZWZ0ZXN0cy5j +b20wHQYDVR0OBBYEFHwS3gs03m6RcpR+66u4OqGiZdYnMA0GCSqGSIb3DQEBCwUA +A4IBAQCFHqMjHUfBZahIsKHQIcFCbC1NFh1ZHlJKZzrRBRwRzX19OttHGMyLpDd6 +tM9Ac6LLR8S4QIWg+HF3IrkN+vfTRDZAccj+tIwBRstmdsEz/rAJ79Vb/00mXZQx +0FPiBDR3hE7On2oo24DU8kJP3v6TrunwtIomVGqrrkwZzvxqyW+WJMB2shGNFw5J +mKYBiiXsHl4Bi7V4zhXssrLp877sqpNLeXloXBmAlT39SwQTP9ImZaV5R6udqlvo +Gfgm5PH/WeK6MV3n5ik0v1rS0LwR2o82WlIB6a4iSEbzY3qSLsWOwt8o5QjAVzCR +tNdbdS3U8nrG73iA2clmF57ARQWC +-----END CERTIFICATE----- diff --git a/knife/spec/data/windows_certificates/test.p7b b/knife/spec/data/windows_certificates/test.p7b Binary files differnew file mode 100644 index 0000000000..bd46d5eccd --- /dev/null +++ b/knife/spec/data/windows_certificates/test.p7b diff --git a/knife/spec/data/windows_certificates/test.pem b/knife/spec/data/windows_certificates/test.pem new file mode 100644 index 0000000000..1c358b5035 --- /dev/null +++ b/knife/spec/data/windows_certificates/test.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgIQX3zqNCJbsKlEvzCz3Z9aNDANBgkqhkiG9w0BAQsFADAh +MR8wHQYDVQQDDBZ3d3cuZHVtbXljaGVmdGVzdHMuY29tMCAXDTIwMDMwNTEwMjcw +NVoYDzIxMjAwMzA1MTAzNzA2WjAhMR8wHQYDVQQDDBZ3d3cuZHVtbXljaGVmdGVz +dHMuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtuYKDb6woWIH +HPPOrcVpgJFVxbkjgk+tsYwbIiqR9jtRaKE6nM/awOgn9/dFF4k8KB8Em0sUx7Vq +J3YhK2N2cAacgP2Frqqf5znpNBBOg968RoZzGx0EiXFvLsqC4y8ggApWTbMXPRk4 +1a7GlpUpSqI3y5cLeEbzwGQKu8I1I+v7P2fTlnJPHarM7sBbL8bieukkFHYu78iV +u1wpKOCCfs5DTmJu8WN+z1Mar9vyrWMBlt2wBBgNHPz5mcXUzJHTzaI/D9RGgBgF +V0IkNqISx/IzR62jjj2g6MgTH4G/0mM6O5sxduM4yGmWZNZpVzh0yMLgH619MZlj +SMQIN3U/SQIDAQABo3MwcTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYB +BQUHAwIGCCsGAQUFBwMBMCEGA1UdEQQaMBiCFnd3dy5kdW1teWNoZWZ0ZXN0cy5j +b20wHQYDVR0OBBYEFHwS3gs03m6RcpR+66u4OqGiZdYnMA0GCSqGSIb3DQEBCwUA +A4IBAQCFHqMjHUfBZahIsKHQIcFCbC1NFh1ZHlJKZzrRBRwRzX19OttHGMyLpDd6 +tM9Ac6LLR8S4QIWg+HF3IrkN+vfTRDZAccj+tIwBRstmdsEz/rAJ79Vb/00mXZQx +0FPiBDR3hE7On2oo24DU8kJP3v6TrunwtIomVGqrrkwZzvxqyW+WJMB2shGNFw5J +mKYBiiXsHl4Bi7V4zhXssrLp877sqpNLeXloXBmAlT39SwQTP9ImZaV5R6udqlvo +Gfgm5PH/WeK6MV3n5ik0v1rS0LwR2o82WlIB6a4iSEbzY3qSLsWOwt8o5QjAVzCR +tNdbdS3U8nrG73iA2clmF57ARQWC +-----END CERTIFICATE----- diff --git a/knife/spec/data/windows_certificates/test.pfx b/knife/spec/data/windows_certificates/test.pfx Binary files differnew file mode 100644 index 0000000000..2c208bf7c6 --- /dev/null +++ b/knife/spec/data/windows_certificates/test.pfx diff --git a/knife/spec/functional/configure_spec.rb b/knife/spec/functional/configure_spec.rb new file mode 100644 index 0000000000..402e988132 --- /dev/null +++ b/knife/spec/functional/configure_spec.rb @@ -0,0 +1,33 @@ +# +# Author:: Bryan McLellan <btm@loftninjas.org> +# 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 "chef/knife/configure" + +describe "knife configure" do + let(:ohai) do + OHAI_SYSTEM + end + + it "loads the fqdn from Ohai" do + knife_configure = Chef::Knife::Configure.new + hostname_guess = ohai[:fqdn] || ohai[:machinename] || ohai[:hostname] || "localhost" + expect(knife_configure.guess_servername).to eql(hostname_guess) + end +end diff --git a/knife/spec/functional/cookbook_delete_spec.rb b/knife/spec/functional/cookbook_delete_spec.rb new file mode 100644 index 0000000000..f25999f0fc --- /dev/null +++ b/knife/spec/functional/cookbook_delete_spec.rb @@ -0,0 +1,156 @@ +# +# Author:: Daniel DeLeo (<dan@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 "tiny_server" + +describe Chef::Knife::CookbookDelete do + let(:server) { TinyServer::Manager.new } + let(:api) { TinyServer::API.instance } + let(:knife_stdout) { StringIO.new } + let(:knife_stderr) { StringIO.new } + let(:knife) do + knife = Chef::Knife::CookbookDelete.new + allow(knife.ui).to receive(:stdout).and_return(knife_stdout) + allow(knife.ui).to receive(:stderr).and_return(knife_stderr) + knife + end + + before(:each) do + server.start + api.clear + + Chef::Config[:node_name] = nil + Chef::Config[:client_key] = nil + Chef::Config[:chef_server_url] = "http://localhost:9000" + end + + after(:each) do + server.stop + end + + context "when the cookbook doesn't exist" do + before do + knife.name_args = %w{no-such-cookbook} + api.get("/cookbooks/no-such-cookbook", 404, Chef::JSONCompat.to_json({ "error" => "dear Tim, no. -Sent from my iPad" })) + end + + it "logs an error and exits" do + expect { knife.run }.to raise_error(SystemExit) + expect(knife_stderr.string).to match(/Cannot find a cookbook named no-such-cookbook to delete/) + end + + end + + context "when there is only one version of a cookbook" do + before do + knife.name_args = %w{obsolete-cookbook} + @cookbook_list = { "obsolete-cookbook" => { "versions" => ["version" => "1.0.0"] } } + api.get("/cookbooks/obsolete-cookbook", 200, Chef::JSONCompat.to_json(@cookbook_list)) + end + + it "asks for confirmation, then deletes the cookbook" do + stdin, stdout = StringIO.new("y\n"), StringIO.new + allow(knife.ui).to receive(:stdin).and_return(stdin) + allow(knife.ui).to receive(:stdout).and_return(stdout) + + cb100_deleted = false + api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" } + + knife.run + + expect(stdout.string).to match(/#{Regexp.escape('Do you really want to delete obsolete-cookbook version 1.0.0? (Y/N)')}/) + expect(cb100_deleted).to be_truthy + end + + it "asks for confirmation before purging" do + knife.config[:purge] = true + + stdin, stdout = StringIO.new("y\ny\n"), StringIO.new + allow(knife.ui).to receive(:stdin).and_return(stdin) + allow(knife.ui).to receive(:stdout).and_return(stdout) + + cb100_deleted = false + api.delete("/cookbooks/obsolete-cookbook/1.0.0?purge=true", 200) { cb100_deleted = true; "[\"true\"]" } + + knife.run + + expect(stdout.string).to match(/#{Regexp.escape('Are you sure you want to purge files')}/) + expect(stdout.string).to match(/#{Regexp.escape('Do you really want to delete obsolete-cookbook version 1.0.0? (Y/N)')}/) + expect(cb100_deleted).to be_truthy + + end + + end + + context "when there are several versions of a cookbook" do + before do + knife.name_args = %w{obsolete-cookbook} + versions = ["1.0.0", "1.1.0", "1.2.0"] + with_version = lambda { |version| { "version" => version } } + @cookbook_list = { "obsolete-cookbook" => { "versions" => versions.map(&with_version) } } + api.get("/cookbooks/obsolete-cookbook", 200, Chef::JSONCompat.to_json(@cookbook_list)) + end + + it "deletes all versions of a cookbook when given the '-a' flag" do + knife.config[:all] = true + knife.config[:yes] = true + cb100_deleted = cb110_deleted = cb120_deleted = nil + api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" } + api.delete("/cookbooks/obsolete-cookbook/1.1.0", 200) { cb110_deleted = true; "[\"true\"]" } + api.delete("/cookbooks/obsolete-cookbook/1.2.0", 200) { cb120_deleted = true; "[\"true\"]" } + knife.run + + expect(cb100_deleted).to be_truthy + expect(cb110_deleted).to be_truthy + expect(cb120_deleted).to be_truthy + end + + it "asks which version to delete and deletes that when not given the -a flag" do + cb100_deleted = cb110_deleted = cb120_deleted = nil + api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" } + stdin, stdout = StringIO.new, StringIO.new + allow(knife.ui).to receive(:stdin).and_return(stdin) + allow(knife.ui).to receive(:stdout).and_return(stdout) + stdin << "1\n" + stdin.rewind + knife.run + expect(cb100_deleted).to be_truthy + expect(stdout.string).to match(/Which version\(s\) do you want to delete\?/) + end + + it "deletes all versions of the cookbook when not given the -a flag and the user chooses to delete all" do + cb100_deleted = cb110_deleted = cb120_deleted = nil + api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" } + api.delete("/cookbooks/obsolete-cookbook/1.1.0", 200) { cb110_deleted = true; "[\"true\"]" } + api.delete("/cookbooks/obsolete-cookbook/1.2.0", 200) { cb120_deleted = true; "[\"true\"]" } + + stdin, stdout = StringIO.new("4\n"), StringIO.new + allow(knife.ui).to receive(:stdin).and_return(stdin) + allow(knife.ui).to receive(:stdout).and_return(stdout) + + knife.run + + expect(cb100_deleted).to be_truthy + expect(cb110_deleted).to be_truthy + expect(cb120_deleted).to be_truthy + end + + end + +end diff --git a/knife/spec/functional/exec_spec.rb b/knife/spec/functional/exec_spec.rb new file mode 100644 index 0000000000..267fe8492e --- /dev/null +++ b/knife/spec/functional/exec_spec.rb @@ -0,0 +1,55 @@ +# +# Author:: Daniel DeLeo (<dan@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 "tiny_server" + +describe Chef::Knife::Exec do + before(:each) do + @server = TinyServer::Manager.new # (:debug => true) + @server.start + end + + after(:each) do + @server.stop + end + + before(:each) do + @knife = Chef::Knife::Exec.new + @api = TinyServer::API.instance + @api.clear + + Chef::Config[:node_name] = nil + Chef::Config[:client_key] = nil + Chef::Config[:chef_server_url] = "http://localhost:9000" + + $output = StringIO.new + end + + it "executes a script in the context of the chef-shell main context" do + @node = Chef::Node.new + @node.name("ohai-world") + response = { "rows" => [@node], "start" => 0, "total" => 1 } + @api.get(%r{^/search/node}, 200, Chef::JSONCompat.to_json(response)) + code = "$output.puts nodes.all" + @knife.config[:exec] = code + @knife.run + expect($output.string).to match(/node\[ohai-world\]/) + end + +end diff --git a/knife/spec/functional/rehash_spec.rb b/knife/spec/functional/rehash_spec.rb new file mode 100644 index 0000000000..a4b7e5507c --- /dev/null +++ b/knife/spec/functional/rehash_spec.rb @@ -0,0 +1,39 @@ +# +# Author:: Daniel DeLeo (<dan@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 "chef/knife/rehash" +require "chef/knife/core/subcommand_loader" + +describe "knife rehash" do + before do + allow(Chef::Knife::SubcommandLoader).to receive(:load_commands) + end + + after do + # We need to clean up the generated manifest or else is messes with later tests + FileUtils.rm_f(Chef::Knife::SubcommandLoader.plugin_manifest_path) + end + + it "writes the loaded plugins to disc" do + knife_rehash = Chef::Knife::Rehash.new + knife_rehash.run + expect(File.read(Chef::Knife::SubcommandLoader.plugin_manifest_path)).to match(/node_list.rb/) + end +end diff --git a/knife/spec/functional/smoke_test.rb b/knife/spec/functional/smoke_test.rb new file mode 100644 index 0000000000..99b61f42f2 --- /dev/null +++ b/knife/spec/functional/smoke_test.rb @@ -0,0 +1,42 @@ +# +# Author:: Daniel DeLeo (<dan@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 "spec_helper" + +describe "knife smoke tests" do + + # Since our specs load all code, there could be a case where knife does not + # run correctly b/c of a missing require, but is not caught by other tests. + # + # We run `knife -v` to verify that knife at least loads all its code. + it "can run and print its version" do + knife_path = File.expand_path("../../bin/knife", CHEF_SPEC_DATA) + knife_cmd = Mixlib::ShellOut.new("#{knife_path} -v") + knife_cmd.run_command + knife_cmd.error! + expect(knife_cmd.stdout).to include(Chef::VERSION) + end + + it "can run and show help" do + knife_path = File.expand_path("../../bin/knife", CHEF_SPEC_DATA) + knife_cmd = Mixlib::ShellOut.new("#{knife_path} --help") + knife_cmd.run_command + knife_cmd.error! + expect(knife_cmd.stdout).to include("Usage") + end +end diff --git a/knife/spec/functional/ssh_spec.rb b/knife/spec/functional/ssh_spec.rb new file mode 100644 index 0000000000..5a29f995f8 --- /dev/null +++ b/knife/spec/functional/ssh_spec.rb @@ -0,0 +1,352 @@ +# +# Author:: Daniel DeLeo (<dan@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 "tiny_server" + +describe Chef::Knife::Ssh do + + before(:each) do + Chef::Knife::Ssh.load_deps + @server = TinyServer::Manager.new + @server.start + end + + after(:each) do + @server.stop + end + + let(:ssh_config) { {} } + before do + allow(Net::SSH).to receive(:configuration_for).and_return(ssh_config) + end + + describe "identity file" do + context "when knife[:ssh_identity_file] is set" do + before do + Chef::Config[:knife][:ssh_identity_file] = "~/.ssh/aws.rsa" + setup_knife(["*:*", "uptime"]) + end + + it "uses the ssh_identity_file" do + @knife.run + expect(@knife.config[:ssh_identity_file]).to eq("~/.ssh/aws.rsa") + end + end + + context "when knife[:ssh_identity_file] is set and frozen" do + before do + Chef::Config[:knife][:ssh_identity_file] = "~/.ssh/aws.rsa".freeze + setup_knife(["*:*", "uptime"]) + end + + it "uses the ssh_identity_file" do + @knife.run + expect(@knife.config[:ssh_identity_file]).to eq("~/.ssh/aws.rsa") + end + end + + context "when -i is provided" do + before do + Chef::Config[:knife][:ssh_identity_file] = nil + setup_knife(["-i ~/.ssh/aws.rsa", "*:*", "uptime"]) + end + + it "should use the value on the command line" do + @knife.run + expect(@knife.config[:ssh_identity_file]).to eq("~/.ssh/aws.rsa") + end + + it "should override what is set in knife.rb" do + Chef::Config[:knife][:ssh_identity_file] = "~/.ssh/other.rsa" + @knife.merge_configs + @knife.run + expect(@knife.config[:ssh_identity_file]).to eq("~/.ssh/aws.rsa") + end + end + + context "when knife[:ssh_identity_file] is not provided]" do + before do + Chef::Config[:knife][:ssh_identity_file] = nil + setup_knife(["*:*", "uptime"]) + end + + it "uses the default" do + @knife.run + expect(@knife.config[:ssh_identity_file]).to eq(nil) + end + end + end + + describe "port" do + context "when -p 31337 is provided" do + before do + setup_knife(["-p 31337", "*:*", "uptime"]) + end + + it "uses the ssh_port" do + @knife.run + expect(@knife.config[:ssh_port]).to eq("31337") + end + end + end + + describe "user" do + context "when knife[:ssh_user] is set" do + before do + Chef::Config[:knife][:ssh_user] = "ubuntu" + setup_knife(["*:*", "uptime"]) + end + + it "uses the ssh_user" do + @knife.run + expect(@knife.config[:ssh_user]).to eq("ubuntu") + end + end + + context "when knife[:ssh_user] is set and frozen" do + before do + Chef::Config[:knife][:ssh_user] = "ubuntu".freeze + setup_knife(["*:*", "uptime"]) + end + + it "uses the ssh_user" do + @knife.run + expect(@knife.config[:ssh_user]).to eq("ubuntu") + end + end + + context "when -x is provided" do + before do + Chef::Config[:knife][:ssh_user] = nil + setup_knife(["-x ubuntu", "*:*", "uptime"]) + end + + it "should use the value on the command line" do + @knife.run + expect(@knife.config[:ssh_user]).to eq("ubuntu") + end + + it "should override what is set in knife.rb" do + Chef::Config[:knife][:ssh_user] = "root" + @knife.merge_configs + @knife.run + expect(@knife.config[:ssh_user]).to eq("ubuntu") + end + end + + context "when knife[:ssh_user] is not provided]" do + before do + Chef::Config[:knife][:ssh_user] = nil + setup_knife(["*:*", "uptime"]) + end + + it "uses the default (current user)" do + @knife.run + expect(@knife.config[:ssh_user]).to eq(nil) + end + end + end + + describe "attribute" do + context "when knife[:ssh_attribute] is set" do + before do + Chef::Config[:knife][:ssh_attribute] = "ec2.public_hostname" + setup_knife(["*:*", "uptime"]) + end + + it "uses the ssh_attribute" do + @knife.run + expect(@knife.get_ssh_attribute({ "target" => "ec2.public_hostname" })).to eq("ec2.public_hostname") + end + end + + context "when knife[:ssh_attribute] is not provided" do + before do + Chef::Config[:knife][:ssh_attribute] = nil + setup_knife(["*:*", "uptime"]) + end + + it "uses the default" do + @knife.run + expect(@knife.get_ssh_attribute({ "fqdn" => "fqdn" })).to eq("fqdn") + end + end + + context "when -a ec2.public_public_hostname is provided" do + before do + Chef::Config[:knife][:ssh_attribute] = nil + setup_knife(["-a", "ec2.public_hostname", "*:*", "uptime"]) + end + + it "should use the value on the command line" do + @knife.run + expect(@knife.config[:ssh_attribute]).to eq("ec2.public_hostname") + end + + it "should override what is set in knife.rb" do + # This is the setting imported from knife.rb + Chef::Config[:knife][:ssh_attribute] = "fqdn" + @knife.merge_configs + # Then we run knife with the -a flag, which sets the above variable + setup_knife(["-a", "ec2.public_hostname", "*:*", "uptime"]) + @knife.run + expect(@knife.config[:ssh_attribute]).to eq("ec2.public_hostname") + end + end + end + + describe "prefix" do + context "when knife[:prefix_attribute] is set" do + before do + Chef::Config[:knife][:prefix_attribute] = "name" + setup_knife(["*:*", "uptime"]) + end + + it "uses the prefix_attribute" do + @knife.run + expect(@knife.get_prefix_attribute({ "prefix" => "name" })).to eq("name") + end + end + + context "when knife[:prefix_attribute] is not provided" do + before do + Chef::Config[:knife][:prefix_attribute] = nil + setup_knife(["*:*", "uptime"]) + end + + it "falls back to nil" do + @knife.run + expect(@knife.get_prefix_attribute({})).to eq(nil) + end + end + + context "when --prefix-attribute ec2.public_public_hostname is provided" do + before do + Chef::Config[:knife][:prefix_attribute] = nil + setup_knife(["--prefix-attribute", "ec2.public_hostname", "*:*", "uptime"]) + end + + it "should use the value on the command line" do + @knife.run + expect(@knife.config[:prefix_attribute]).to eq("ec2.public_hostname") + end + + it "should override what is set in knife.rb" do + # This is the setting imported from knife.rb + Chef::Config[:knife][:prefix_attribute] = "fqdn" + @knife.merge_configs + # Then we run knife with the -b flag, which sets the above variable + setup_knife(["--prefix-attribute", "ec2.public_hostname", "*:*", "uptime"]) + @knife.run + expect(@knife.config[:prefix_attribute]).to eq("ec2.public_hostname") + end + end + end + + describe "gateway" do + context "when knife[:ssh_gateway] is set" do + before do + Chef::Config[:knife][:ssh_gateway] = "user@ec2.public_hostname" + setup_knife(["*:*", "uptime"]) + end + + it "uses the ssh_gateway" do + expect(@knife.session).to receive(:via).with("ec2.public_hostname", "user", { append_all_supported_algorithms: true }) + @knife.run + expect(@knife.config[:ssh_gateway]).to eq("user@ec2.public_hostname") + end + end + + context "when -G user@ec2.public_hostname is provided" do + before do + Chef::Config[:knife][:ssh_gateway] = nil + setup_knife(["-G user@ec2.public_hostname", "*:*", "uptime"]) + end + + it "uses the ssh_gateway" do + expect(@knife.session).to receive(:via).with("ec2.public_hostname", "user", { append_all_supported_algorithms: true }) + @knife.run + expect(@knife.config[:ssh_gateway]).to eq("user@ec2.public_hostname") + end + end + + context "when knife[:ssh_gateway_identity] is set" do + before do + Chef::Config[:knife][:ssh_gateway] = "user@ec2.public_hostname" + Chef::Config[:knife][:ssh_gateway_identity] = "~/.ssh/aws-gateway.rsa" + setup_knife(["*:*", "uptime"]) + end + + it "uses the ssh_gateway_identity file" do + expect(@knife.session).to receive(:via).with("ec2.public_hostname", "user", { append_all_supported_algorithms: true, keys: File.expand_path("#{ENV["HOME"]}/.ssh/aws-gateway.rsa").squeeze("/"), keys_only: true }) + @knife.run + expect(@knife.config[:ssh_gateway_identity]).to eq("~/.ssh/aws-gateway.rsa") + end + end + + context "when -ssh-gateway-identity is provided and knife[:ssh_gateway] is set" do + before do + Chef::Config[:knife][:ssh_gateway] = "user@ec2.public_hostname" + Chef::Config[:knife][:ssh_gateway_identity] = nil + setup_knife(["--ssh-gateway-identity", "~/.ssh/aws-gateway.rsa", "*:*", "uptime"]) + end + + it "uses the ssh_gateway_identity file" do + expect(@knife.session).to receive(:via).with("ec2.public_hostname", "user", { append_all_supported_algorithms: true, keys: File.expand_path("#{ENV["HOME"]}/.ssh/aws-gateway.rsa").squeeze("/"), keys_only: true }) + @knife.run + expect(@knife.config[:ssh_gateway_identity]).to eq("~/.ssh/aws-gateway.rsa") + end + end + + context "when the gateway requires a password" do + before do + Chef::Config[:knife][:ssh_gateway] = nil + setup_knife(["-G user@ec2.public_hostname", "*:*", "uptime"]) + allow(@knife.session).to receive(:via) do |host, user, options| + raise Net::SSH::AuthenticationFailed unless options[:password] + end + end + + it "should prompt the user for a password" do + expect(@knife.ui).to receive(:ask).with("Enter the password for user@ec2.public_hostname: ", echo: false).and_return("password") + @knife.run + end + end + end + + def setup_knife(params = []) + @knife = Chef::Knife::Ssh.new(params) + # We explicitly avoid running #configure_chef, which would read a knife.rb + # if available, but #merge_configs (which is called by #configure_chef) is + # necessary to have default options merged in. + @knife.merge_configs + allow(@knife).to receive(:ssh_command) { 0 } + @api = TinyServer::API.instance + @api.clear + + Chef::Config[:node_name] = nil + Chef::Config[:client_key] = nil + Chef::Config[:chef_server_url] = "http://localhost:9000" + + @api.post("/search/node?q=*:*&start=0&rows=1000", 200) do + %({"total":1, "start":0, "rows":[{"data": {"fqdn":"the.fqdn", "target": "the_public_hostname"}}]}) + end + end + +end diff --git a/knife/spec/functional/version_spec.rb b/knife/spec/functional/version_spec.rb new file mode 100644 index 0000000000..b024cc1cda --- /dev/null +++ b/knife/spec/functional/version_spec.rb @@ -0,0 +1,26 @@ +# 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 "chef/mixin/shell_out" + +describe "Knife Version", :executables do + include Chef::Mixin::ShellOut + let(:knife_dir) { File.join(__dir__, "..", "..", "..", "knife") } + xit "should be sane" do + expect(shell_out!("bundle exec knife -v", cwd: knife_dir).stdout.chomp).to match(/.*: #{Chef::Knife::VERSION}/) + end +end + diff --git a/knife/spec/integration/chef_fs_data_store_spec.rb b/knife/spec/integration/chef_fs_data_store_spec.rb new file mode 100644 index 0000000000..fda06164a4 --- /dev/null +++ b/knife/spec/integration/chef_fs_data_store_spec.rb @@ -0,0 +1,557 @@ +# +# 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/list" +require "chef/knife/delete" +require "chef/knife/show" +require "chef/knife/raw" +require "chef/knife/cookbook_upload" + +describe "ChefFSDataStore tests", :workstation do + include IntegrationSupport + include KnifeSupport + + let(:cookbook_x_100_metadata_rb) { cb_metadata("x", "1.0.0") } + let(:cookbook_z_100_metadata_rb) { cb_metadata("z", "1.0.0") } + + describe "with repo mode 'hosted_everything' (default)" do + before do + Chef::Config.chef_zero.osc_compat = false + end + + when_the_repository "has one of each thing" do + before do + file "clients/x.json", {} + file "cookbook_artifacts/x-111/metadata.rb", cookbook_x_100_metadata_rb + file "cookbooks/x/metadata.rb", cookbook_x_100_metadata_rb + file "data_bags/x/y.json", {} + file "environments/x.json", {} + file "nodes/x.json", {} + file "roles/x.json", {} + # file "users/x.json", {} + file "containers/x.json", {} + file "groups/x.json", {} + file "containers/x.json", {} + file "groups/x.json", {} + file "policies/x-111.json", {} + file "policy_groups/x.json", {} + end + + context "GET /TYPE" do + it "knife list -z -R returns everything" do + knife("list -z -Rfp /").should_succeed <<~EOM + /acls/ + /acls/clients/ + /acls/clients/x.json + /acls/containers/ + /acls/containers/x.json + /acls/cookbook_artifacts/ + /acls/cookbook_artifacts/x.json + /acls/cookbooks/ + /acls/cookbooks/x.json + /acls/data_bags/ + /acls/data_bags/x.json + /acls/environments/ + /acls/environments/x.json + /acls/groups/ + /acls/groups/x.json + /acls/nodes/ + /acls/nodes/x.json + /acls/organization.json + /acls/policies/ + /acls/policies/x.json + /acls/policy_groups/ + /acls/policy_groups/x.json + /acls/roles/ + /acls/roles/x.json + /clients/ + /clients/x.json + /containers/ + /containers/x.json + /cookbook_artifacts/ + /cookbook_artifacts/x-111/ + /cookbook_artifacts/x-111/metadata.rb + /cookbooks/ + /cookbooks/x/ + /cookbooks/x/metadata.rb + /data_bags/ + /data_bags/x/ + /data_bags/x/y.json + /environments/ + /environments/x.json + /groups/ + /groups/x.json + /invitations.json + /members.json + /nodes/ + /nodes/x.json + /org.json + /policies/ + /policies/x-111.json + /policy_groups/ + /policy_groups/x.json + /roles/ + /roles/x.json + EOM + end + end + + context "DELETE /TYPE/NAME" do + it "knife delete -z /clients/x.json works" do + knife("delete -z /clients/x.json").should_succeed "Deleted /clients/x.json\n" + knife("list -z -Rfp /clients").should_succeed "" + end + + it "knife delete -z -r /cookbooks/x works" do + knife("delete -z -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n" + knife("list -z -Rfp /cookbooks").should_succeed "" + end + + it "knife delete -z -r /data_bags/x works" do + knife("delete -z -r /data_bags/x").should_succeed "Deleted /data_bags/x\n" + knife("list -z -Rfp /data_bags").should_succeed "" + end + + it "knife delete -z /data_bags/x/y.json works" do + knife("delete -z /data_bags/x/y.json").should_succeed "Deleted /data_bags/x/y.json\n" + knife("list -z -Rfp /data_bags").should_succeed "/data_bags/x/\n" + end + + it "knife delete -z /environments/x.json works" do + knife("delete -z /environments/x.json").should_succeed "Deleted /environments/x.json\n" + knife("list -z -Rfp /environments").should_succeed "" + end + + it "knife delete -z /nodes/x.json works" do + knife("delete -z /nodes/x.json").should_succeed "Deleted /nodes/x.json\n" + knife("list -z -Rfp /nodes").should_succeed "" + end + + it "knife delete -z /roles/x.json works" do + knife("delete -z /roles/x.json").should_succeed "Deleted /roles/x.json\n" + knife("list -z -Rfp /roles").should_succeed "" + end + + end + + context "GET /TYPE/NAME" do + it "knife show -z /clients/x.json works" do + knife("show -z /clients/x.json").should_succeed( /"x"/ ) + end + + it "knife show -z /cookbooks/x/metadata.rb works" do + knife("show -z /cookbooks/x/metadata.rb").should_succeed "/cookbooks/x/metadata.rb:\n#{cookbook_x_100_metadata_rb}\n" + end + + it "knife show -z /data_bags/x/y.json works" do + knife("show -z /data_bags/x/y.json").should_succeed( /"y"/ ) + end + + it "knife show -z /environments/x.json works" do + knife("show -z /environments/x.json").should_succeed( /"x"/ ) + end + + it "knife show -z /nodes/x.json works" do + knife("show -z /nodes/x.json").should_succeed( /"x"/ ) + end + + it "knife show -z /roles/x.json works" do + knife("show -z /roles/x.json").should_succeed( /"x"/ ) + end + + end + + context "PUT /TYPE/NAME" do + before do + file "empty.json", {} + file "dummynode.json", { "name" => "x", "chef_environment" => "rspec" , "json_class" => "Chef::Node", "normal" => { "foo" => "bar" } } + file "rolestuff.json", '{"description":"hi there","name":"x"}' + file "cookbooks_to_upload/x/metadata.rb", cookbook_x_100_metadata_rb + end + + it "knife raw -z -i empty.json -m PUT /clients/x" do + knife("raw -z -i #{path_to("empty.json")} -m PUT /clients/x").should_succeed( /"x"/ ) + knife("list --local /clients").should_succeed "/clients/x.json\n" + end + + it "knife cookbook upload works" do + knife("cookbook upload -z --cookbook-path #{path_to("cookbooks_to_upload")} x").should_succeed stderr: <<~EOM + Uploading x [1.0.0] + Uploaded 1 cookbook. + EOM + knife("list --local -Rfp /cookbooks").should_succeed "/cookbooks/x/\n/cookbooks/x/metadata.json\n/cookbooks/x/metadata.rb\n" + end + + it "knife raw -z -i empty.json -m PUT /data/x/y" do + knife("raw -z -i #{path_to("empty.json")} -m PUT /data/x/y").should_succeed( /"y"/ ) + knife("list --local -Rfp /data_bags").should_succeed "/data_bags/x/\n/data_bags/x/y.json\n" + end + + it "knife raw -z -i empty.json -m PUT /environments/x" do + knife("raw -z -i #{path_to("empty.json")} -m PUT /environments/x").should_succeed( /"x"/ ) + knife("list --local /environments").should_succeed "/environments/x.json\n" + end + + it "knife raw -z -i dummynode.json -m PUT /nodes/x" do + knife("raw -z -i #{path_to("dummynode.json")} -m PUT /nodes/x").should_succeed( /"x"/ ) + knife("list --local /nodes").should_succeed "/nodes/x.json\n" + knife("show -z /nodes/x.json --verbose").should_succeed(/"bar"/) + end + + it "knife raw -z -i empty.json -m PUT /roles/x" do + knife("raw -z -i #{path_to("empty.json")} -m PUT /roles/x").should_succeed( /"x"/ ) + knife("list --local /roles").should_succeed "/roles/x.json\n" + end + + it "After knife raw -z -i rolestuff.json -m PUT /roles/x, the output is pretty" do + knife("raw -z -i #{path_to("rolestuff.json")} -m PUT /roles/x").should_succeed( /"x"/ ) + expect(IO.read(path_to("roles/x.json"))).to eq <<~EOM.strip + { + "name": "x", + "description": "hi there" + } + EOM + end + end + end + + when_the_repository "is empty" do + context "POST /TYPE/NAME" do + before do + file "empty.json", { "name" => "z" } + file "dummynode.json", { "name" => "z", "chef_environment" => "rspec" , "json_class" => "Chef::Node", "normal" => { "foo" => "bar" } } + file "empty_x.json", { "name" => "x" } + file "empty_id.json", { "id" => "z" } + file "rolestuff.json", '{"description":"hi there","name":"x"}' + file "cookbooks_to_upload/z/metadata.rb", cookbook_z_100_metadata_rb + end + + it "knife raw -z -i empty.json -m POST /clients" do + knife("raw -z -i #{path_to("empty.json")} -m POST /clients").should_succeed( /uri/ ) + knife("list --local /clients").should_succeed "/clients/z.json\n" + end + + it "knife cookbook upload works" do + knife("cookbook upload -z --cookbook-path #{path_to("cookbooks_to_upload")} z").should_succeed stderr: <<~EOM + Uploading z [1.0.0] + Uploaded 1 cookbook. + EOM + knife("list --local -Rfp /cookbooks").should_succeed "/cookbooks/z/\n/cookbooks/z/metadata.json\n/cookbooks/z/metadata.rb\n" + end + + it "knife raw -z -i empty.json -m POST /data" do + knife("raw -z -i #{path_to("empty.json")} -m POST /data").should_succeed( /uri/ ) + knife("list --local -Rfp /data_bags").should_succeed "/data_bags/z/\n" + end + + it "knife raw -z -i empty.json -m POST /data/x" do + knife("raw -z -i #{path_to("empty_x.json")} -m POST /data").should_succeed( /uri/ ) + knife("raw -z -i #{path_to("empty_id.json")} -m POST /data/x").should_succeed( /"z"/ ) + knife("list --local -Rfp /data_bags").should_succeed "/data_bags/x/\n/data_bags/x/z.json\n" + end + + it "knife raw -z -i empty.json -m POST /environments" do + knife("raw -z -i #{path_to("empty.json")} -m POST /environments").should_succeed( /uri/ ) + knife("list --local /environments").should_succeed "/environments/z.json\n" + end + + it "knife raw -z -i dummynode.json -m POST /nodes" do + knife("raw -z -i #{path_to("dummynode.json")} -m POST /nodes").should_succeed( /uri/ ) + knife("list --local /nodes").should_succeed "/nodes/z.json\n" + knife("show -z /nodes/z.json").should_succeed(/"bar"/) + end + + it "knife raw -z -i empty.json -m POST /roles" do + knife("raw -z -i #{path_to("empty.json")} -m POST /roles").should_succeed( /uri/ ) + knife("list --local /roles").should_succeed "/roles/z.json\n" + end + + it "After knife raw -z -i rolestuff.json -m POST /roles, the output is pretty" do + knife("raw -z -i #{path_to("rolestuff.json")} -m POST /roles").should_succeed( /uri/ ) + expect(IO.read(path_to("roles/x.json"))).to eq <<~EOM.strip + { + "name": "x", + "description": "hi there" + } + EOM + end + end + + it "knife list -z -R returns nothing" do + knife("list -z -Rfp /").should_succeed <<~EOM + /acls/ + /acls/clients/ + /acls/containers/ + /acls/cookbook_artifacts/ + /acls/cookbooks/ + /acls/data_bags/ + /acls/environments/ + /acls/groups/ + /acls/nodes/ + /acls/organization.json + /acls/policies/ + /acls/policy_groups/ + /acls/roles/ + /clients/ + /containers/ + /cookbook_artifacts/ + /cookbooks/ + /data_bags/ + /environments/ + /groups/ + /invitations.json + /members.json + /nodes/ + /org.json + /policies/ + /policy_groups/ + /roles/ + EOM + end + + context "DELETE /TYPE/NAME" do + it "knife delete -z /clients/x.json fails with an error" do + knife("delete -z /clients/x.json").should_fail "ERROR: /clients/x.json: No such file or directory\n" + end + + it "knife delete -z -r /cookbooks/x fails with an error" do + knife("delete -z -r /cookbooks/x").should_fail "ERROR: /cookbooks/x: No such file or directory\n" + end + + it "knife delete -z -r /data_bags/x fails with an error" do + knife("delete -z -r /data_bags/x").should_fail "ERROR: /data_bags/x: No such file or directory\n" + end + + it "knife delete -z /data_bags/x/y.json fails with an error" do + knife("delete -z /data_bags/x/y.json").should_fail "ERROR: /data_bags/x/y.json: No such file or directory\n" + end + + it "knife delete -z /environments/x.json fails with an error" do + knife("delete -z /environments/x.json").should_fail "ERROR: /environments/x.json: No such file or directory\n" + end + + it "knife delete -z /nodes/x.json fails with an error" do + knife("delete -z /nodes/x.json").should_fail "ERROR: /nodes/x.json: No such file or directory\n" + end + + it "knife delete -z /roles/x.json fails with an error" do + knife("delete -z /roles/x.json").should_fail "ERROR: /roles/x.json: No such file or directory\n" + end + + end + + context "GET /TYPE/NAME" do + it "knife show -z /clients/x.json fails with an error" do + knife("show -z /clients/x.json").should_fail "ERROR: /clients/x.json: No such file or directory\n" + end + + it "knife show -z /cookbooks/x/metadata.rb fails with an error" do + knife("show -z /cookbooks/x/metadata.rb").should_fail "ERROR: /cookbooks/x/metadata.rb: No such file or directory\n" + end + + it "knife show -z /data_bags/x/y.json fails with an error" do + knife("show -z /data_bags/x/y.json").should_fail "ERROR: /data_bags/x/y.json: No such file or directory\n" + end + + it "knife show -z /environments/x.json fails with an error" do + knife("show -z /environments/x.json").should_fail "ERROR: /environments/x.json: No such file or directory\n" + end + + it "knife show -z /nodes/x.json fails with an error" do + knife("show -z /nodes/x.json").should_fail "ERROR: /nodes/x.json: No such file or directory\n" + end + + it "knife show -z /roles/x.json fails with an error" do + knife("show -z /roles/x.json").should_fail "ERROR: /roles/x.json: No such file or directory\n" + end + + end + + context "PUT /TYPE/NAME" do + before do + file "empty.json", {} + end + + it "knife raw -z -i empty.json -m PUT /clients/x fails with 404" do + knife("raw -z -i #{path_to("empty.json")} -m PUT /clients/x").should_fail( /404/ ) + end + + it "knife raw -z -i empty.json -m PUT /data/x/y fails with 404" do + knife("raw -z -i #{path_to("empty.json")} -m PUT /data/x/y").should_fail( /404/ ) + end + + it "knife raw -z -i empty.json -m PUT /environments/x fails with 404" do + knife("raw -z -i #{path_to("empty.json")} -m PUT /environments/x").should_fail( /404/ ) + end + + it "knife raw -z -i empty.json -m PUT /nodes/x fails with 404" do + knife("raw -z -i #{path_to("empty.json")} -m PUT /nodes/x").should_fail( /404/ ) + end + + it "knife raw -z -i empty.json -m PUT /roles/x fails with 404" do + knife("raw -z -i #{path_to("empty.json")} -m PUT /roles/x").should_fail( /404/ ) + end + + end + end + end + + # We have to configure Zero for Chef 11 mode in order to test users because: + # 1. local mode overrides your `chef_server_url` to something like "http://localhost:PORT" + # 2. single org mode maps requests like "https://localhost:PORT/users" so + # they're functionally equivalent to "https://localhost:PORT/organizations/DEFAULT/users" + # 3. Users are global objects in Chef 12, and should be accessed at URLs like + # "https://localhost:PORT/users" (there is an org-specific users endpoint, + # but it's for listing users in an org, not for managing users). + # 4. Therefore you can't hit the _real_ users endpoint in local mode when + # configured for Chef Server 12 mode. + # + # Because of this, we have to configure Zero for Chef 11 OSC mode in order to + # test the users part of the data store with local mode. + describe "with repo mode 'everything'" do + before do + Chef::Config.repo_mode = "everything" + Chef::Config.chef_zero.osc_compat = true + end + + when_the_repository "has one of each thing" do + before do + file "clients/x.json", {} + file "cookbooks/x/metadata.rb", cookbook_x_100_metadata_rb + file "data_bags/x/y.json", {} + file "environments/x.json", {} + file "nodes/x.json", {} + file "roles/x.json", {} + file "users/x.json", {} + end + + context "GET /TYPE" do + it "knife list -z -R returns everything" do + knife("list -z -Rfp /").should_succeed <<~EOM + /clients/ + /clients/x.json + /cookbooks/ + /cookbooks/x/ + /cookbooks/x/metadata.rb + /data_bags/ + /data_bags/x/ + /data_bags/x/y.json + /environments/ + /environments/x.json + /nodes/ + /nodes/x.json + /roles/ + /roles/x.json + /users/ + /users/x.json + EOM + end + end + + context "DELETE /TYPE/NAME" do + it "knife delete -z /users/x.json works" do + knife("delete -z /users/x.json").should_succeed "Deleted /users/x.json\n" + knife("list -z -Rfp /users").should_succeed "" + end + end + + context "GET /TYPE/NAME" do + it "knife show -z /users/x.json works" do + knife("show -z /users/x.json").should_succeed( /"x"/ ) + end + end + + context "PUT /TYPE/NAME" do + before do + file "empty.json", {} + file "dummynode.json", { "name" => "x", "chef_environment" => "rspec" , "json_class" => "Chef::Node", "normal" => { "foo" => "bar" } } + file "rolestuff.json", '{"description":"hi there","name":"x"}' + file "cookbooks_to_upload/x/metadata.rb", cookbook_x_100_metadata_rb + end + + it "knife raw -z -i empty.json -m PUT /users/x" do + knife("raw -z -i #{path_to("empty.json")} -m PUT /users/x").should_succeed( /"x"/ ) + knife("list --local /users").should_succeed "/users/x.json\n" + end + + it "After knife raw -z -i rolestuff.json -m PUT /roles/x, the output is pretty" do + knife("raw -z -i #{path_to("rolestuff.json")} -m PUT /roles/x").should_succeed( /"x"/ ) + expect(IO.read(path_to("roles/x.json"))).to eq <<~EOM.strip + { + "name": "x", + "description": "hi there" + } + EOM + end + end + end + + when_the_repository "is empty" do + context "POST /TYPE/NAME" do + before do + file "empty.json", { "name" => "z" } + file "dummynode.json", { "name" => "z", "chef_environment" => "rspec" , "json_class" => "Chef::Node", "normal" => { "foo" => "bar" } } + file "empty_x.json", { "name" => "x" } + file "empty_id.json", { "id" => "z" } + file "rolestuff.json", '{"description":"hi there","name":"x"}' + file "cookbooks_to_upload/z/metadata.rb", cookbook_z_100_metadata_rb + end + + it "knife raw -z -i empty.json -m POST /users" do + knife("raw -z -i #{path_to("empty.json")} -m POST /users").should_succeed( /uri/ ) + knife("list --local /users").should_succeed "/users/z.json\n" + end + end + + it "knife list -z -R returns nothing" do + knife("list -z -Rfp /").should_succeed <<~EOM + /clients/ + /cookbooks/ + /data_bags/ + /environments/ + /nodes/ + /roles/ + /users/ + EOM + end + + context "DELETE /TYPE/NAME" do + it "knife delete -z /users/x.json fails with an error" do + knife("delete -z /users/x.json").should_fail "ERROR: /users/x.json: No such file or directory\n" + end + end + + context "GET /TYPE/NAME" do + it "knife show -z /users/x.json fails with an error" do + knife("show -z /users/x.json").should_fail "ERROR: /users/x.json: No such file or directory\n" + end + end + + context "PUT /TYPE/NAME" do + before do + file "empty.json", {} + end + + it "knife raw -z -i empty.json -m PUT /users/x fails with 404" do + knife("raw -z -i #{path_to("empty.json")} -m PUT /users/x").should_fail( /404/ ) + end + end + end + end +end diff --git a/knife/spec/integration/chef_repo_path_spec.rb b/knife/spec/integration/chef_repo_path_spec.rb new file mode 100644 index 0000000000..27b45ac428 --- /dev/null +++ b/knife/spec/integration/chef_repo_path_spec.rb @@ -0,0 +1,962 @@ +# +# 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 "support/shared/context/config" +require "chef/knife/list" +require "chef/knife/show" + +describe "chef_repo_path tests", :workstation do + include IntegrationSupport + include KnifeSupport + + let(:error_rel_path_outside_repo) { /^ERROR: Attempt to use relative path '' when current directory is outside the repository path/ } + + # TODO alternate repo_path / *_path + context "alternate *_path" do + when_the_repository "has clients and clients2, cookbooks and cookbooks2, etc." do + before do + file "clients/client1.json", {} + file "cookbooks/cookbook1/metadata.rb", "" + file "data_bags/bag/item.json", {} + file "environments/env1.json", {} + file "nodes/node1.json", {} + file "roles/role1.json", {} + file "users/user1.json", {} + + file "clients2/client2.json", {} + file "cookbooks2/cookbook2/metadata.rb", "" + file "data_bags2/bag2/item2.json", {} + file "environments2/env2.json", {} + file "nodes2/node2.json", {} + file "roles2/role2.json", {} + file "users2/user2.json", {} + + directory "chef_repo2" do + file "clients/client3.json", {} + file "cookbooks/cookbook3/metadata.rb", "name 'cookbook3'" + file "data_bags/bag3/item3.json", {} + file "environments/env3.json", {} + file "nodes/node3.json", {} + file "roles/role3.json", {} + file "users/user3.json", {} + end + end + + it "knife list --local -Rfp --chef-repo-path chef_repo2 / grabs chef_repo2 stuff" do + Chef::Config.delete(:chef_repo_path) + knife("list --local -Rfp --chef-repo-path #{path_to("chef_repo2")} /").should_succeed <<~EOM + /clients/ + /clients/client3.json + /cookbooks/ + /cookbooks/cookbook3/ + /cookbooks/cookbook3/metadata.rb + /data_bags/ + /data_bags/bag3/ + /data_bags/bag3/item3.json + /environments/ + /environments/env3.json + /nodes/ + /nodes/node3.json + /roles/ + /roles/role3.json + /users/ + /users/user3.json + EOM + end + + # "Skipping for BK... As Windows 2019 has 8dot3name disabled by default" + it "knife list --local -Rfp --chef-repo-path chef_r~1 / grabs chef_repo2 stuff", :windows_only, :skip_buildkite do + Chef::Config.delete(:chef_repo_path) + knife("list --local -Rfp --chef-repo-path #{path_to("chef_r~1")} /").should_succeed <<~EOM + /clients/ + /clients/client3.json + /cookbooks/ + /cookbooks/cookbook3/ + /cookbooks/cookbook3/metadata.rb + /data_bags/ + /data_bags/bag3/ + /data_bags/bag3/item3.json + /environments/ + /environments/env3.json + /nodes/ + /nodes/node3.json + /roles/ + /roles/role3.json + /users/ + /users/user3.json + EOM + end + + # "Skipping for BK... As Windows 2019 has 8dot3name disabled by default" + it "knife list --local -Rfp --chef-repo-path chef_r~1 / grabs chef_repo2 stuff", :windows_only, :skip_buildkite do + Chef::Config.delete(:chef_repo_path) + knife("list -z -Rfp --chef-repo-path #{path_to("chef_r~1")} /").should_succeed <<~EOM + /acls/ + /acls/clients/ + /acls/clients/client3.json + /acls/containers/ + /acls/cookbook_artifacts/ + /acls/cookbooks/ + /acls/cookbooks/cookbook3.json + /acls/data_bags/ + /acls/data_bags/bag3.json + /acls/environments/ + /acls/environments/env3.json + /acls/groups/ + /acls/nodes/ + /acls/nodes/node3.json + /acls/organization.json + /acls/policies/ + /acls/policy_groups/ + /acls/roles/ + /acls/roles/role3.json + /clients/ + /clients/client3.json + /containers/ + /cookbook_artifacts/ + /cookbooks/ + /cookbooks/cookbook3/ + /cookbooks/cookbook3/metadata.rb + /data_bags/ + /data_bags/bag3/ + /data_bags/bag3/item3.json + /environments/ + /environments/env3.json + /groups/ + /invitations.json + /members.json + /nodes/ + /nodes/node3.json + /org.json + /policies/ + /policy_groups/ + /roles/ + /roles/role3.json + EOM + end + + context "when all _paths are set to alternates" do + before :each do + %w{client cookbook data_bag environment node role user}.each do |object_name| + Chef::Config["#{object_name}_path".to_sym] = File.join(Chef::Config.chef_repo_path, "#{object_name}s2") + end + Chef::Config.chef_repo_path = File.join(Chef::Config.chef_repo_path, "chef_repo2") + end + + it "knife list --local -Rfp --chef-repo-path chef_repo2 / grabs chef_repo2 stuff" do + knife("list --local -Rfp --chef-repo-path #{path_to("chef_repo2")} /").should_succeed <<~EOM + /clients/ + /clients/client3.json + /cookbooks/ + /cookbooks/cookbook3/ + /cookbooks/cookbook3/metadata.rb + /data_bags/ + /data_bags/bag3/ + /data_bags/bag3/item3.json + /environments/ + /environments/env3.json + /nodes/ + /nodes/node3.json + /roles/ + /roles/role3.json + /users/ + /users/user3.json + EOM + end + + context "when cwd is at the top level" do + before { cwd "." } + it "knife list --local -Rfp fails" do + knife("list --local -Rfp").should_fail(error_rel_path_outside_repo) + end + end + + context "when cwd is inside the data_bags directory" do + before { cwd "data_bags" } + it "knife list --local -Rfp fails" do + knife("list --local -Rfp").should_fail(error_rel_path_outside_repo) + end + end + + context "when cwd is inside chef_repo2" do + before { cwd "chef_repo2" } + it "knife list --local -Rfp lists everything" do + knife("list --local -Rfp").should_succeed <<~EOM + clients/ + clients/client2.json + cookbooks/ + cookbooks/cookbook2/ + cookbooks/cookbook2/metadata.rb + data_bags/ + data_bags/bag2/ + data_bags/bag2/item2.json + environments/ + environments/env2.json + nodes/ + nodes/node2.json + roles/ + roles/role2.json + users/ + users/user2.json + EOM + end + end + + context "when cwd is inside data_bags2" do + before { cwd "data_bags2" } + it "knife list --local -Rfp lists data bags" do + knife("list --local -Rfp").should_succeed <<~EOM + bag2/ + bag2/item2.json + EOM + end + it "knife list --local -Rfp ../roles lists roles" do + knife("list --local -Rfp ../roles").should_succeed "/roles/role2.json\n" + end + end + end + + context "when all _paths except chef_repo_path are set to alternates" do + before :each do + %w{client cookbook data_bag environment node role user}.each do |object_name| + Chef::Config["#{object_name}_path".to_sym] = File.join(Chef::Config.chef_repo_path, "#{object_name}s2") + end + end + + context "when cwd is at the top level" do + before { cwd "." } + it "knife list --local -Rfp lists everything" do + knife("list --local -Rfp").should_succeed <<~EOM + clients/ + clients/client2.json + cookbooks/ + cookbooks/cookbook2/ + cookbooks/cookbook2/metadata.rb + data_bags/ + data_bags/bag2/ + data_bags/bag2/item2.json + environments/ + environments/env2.json + nodes/ + nodes/node2.json + roles/ + roles/role2.json + users/ + users/user2.json + EOM + end + end + + context "when cwd is inside the data_bags directory" do + before { cwd "data_bags" } + it "knife list --local -Rfp fails" do + knife("list --local -Rfp").should_fail(error_rel_path_outside_repo) + end + end + + context "when cwd is inside chef_repo2" do + before { cwd "chef_repo2" } + it "knife list -Rfp fails" do + knife("list --local -Rfp").should_fail(error_rel_path_outside_repo) + end + end + + context "when cwd is inside data_bags2" do + before { cwd "data_bags2" } + it "knife list --local -Rfp lists data bags" do + knife("list --local -Rfp").should_succeed <<~EOM + bag2/ + bag2/item2.json + EOM + end + end + end + + context "when only chef_repo_path is set to its alternate" do + before :each do + %w{client cookbook data_bag environment node role user}.each do |object_name| + Chef::Config.delete("#{object_name}_path".to_sym) + end + Chef::Config.chef_repo_path = File.join(Chef::Config.chef_repo_path, "chef_repo2") + end + + context "when cwd is at the top level" do + before { cwd "." } + it "knife list --local -Rfp fails" do + knife("list --local -Rfp").should_fail(error_rel_path_outside_repo) + end + end + + context "when cwd is inside the data_bags directory" do + before { cwd "data_bags" } + it "knife list --local -Rfp fails" do + knife("list --local -Rfp").should_fail(error_rel_path_outside_repo) + end + end + + context "when cwd is inside chef_repo2" do + before { cwd "chef_repo2" } + it "knife list --local -Rfp lists everything" do + knife("list --local -Rfp").should_succeed <<~EOM + clients/ + clients/client3.json + cookbooks/ + cookbooks/cookbook3/ + cookbooks/cookbook3/metadata.rb + data_bags/ + data_bags/bag3/ + data_bags/bag3/item3.json + environments/ + environments/env3.json + nodes/ + nodes/node3.json + roles/ + roles/role3.json + users/ + users/user3.json + EOM + end + end + + context "when cwd is inside chef_repo2/data_bags" do + before { cwd "chef_repo2/data_bags" } + it "knife list --local -Rfp lists data bags" do + knife("list --local -Rfp").should_succeed <<~EOM + bag3/ + bag3/item3.json + EOM + end + end + end + + context "when paths are set to point to both versions of each" do + before :each do + %w{client cookbook data_bag environment node role user}.each do |object_name| + Chef::Config["#{object_name}_path".to_sym] = [ + File.join(Chef::Config.chef_repo_path, "#{object_name}s"), + File.join(Chef::Config.chef_repo_path, "#{object_name}s2"), + ] + end + Chef::Config.chef_repo_path = File.join(Chef::Config.chef_repo_path, "chef_repo2") + end + + context "when there is a directory in clients1 and file in clients2 with the same name" do + before do + directory "clients/blah.json" + file "clients2/blah.json", {} + end + it "knife show /clients/blah.json succeeds" do + knife("show --local /clients/blah.json").should_succeed <<~EOM + /clients/blah.json: + { + + } + EOM + end + end + + context "when there is a file in cookbooks1 and directory in cookbooks2 with the same name" do + before do + file "cookbooks/blah", "" + file "cookbooks2/blah/metadata.rb", "" + end + it "knife list -Rfp cookbooks shows files in blah" do + knife("list --local -Rfp /cookbooks").should_succeed <<~EOM + /cookbooks/blah/ + /cookbooks/blah/metadata.rb + /cookbooks/cookbook1/ + /cookbooks/cookbook1/metadata.rb + /cookbooks/cookbook2/ + /cookbooks/cookbook2/metadata.rb + EOM + end + end + + context "when there is an empty directory in cookbooks1 and a real cookbook in cookbooks2 with the same name" do + before do + directory "cookbooks/blah" + file "cookbooks2/blah/metadata.rb", "" + end + it "knife list -Rfp cookbooks shows files in blah" do + knife("list --local -Rfp /cookbooks").should_succeed(<<~EOM, stderr: "WARN: Cookbook 'blah' is empty or entirely chefignored at #{Chef::Config.cookbook_path[0]}/blah\n") + /cookbooks/blah/ + /cookbooks/blah/metadata.rb + /cookbooks/cookbook1/ + /cookbooks/cookbook1/metadata.rb + /cookbooks/cookbook2/ + /cookbooks/cookbook2/metadata.rb + EOM + end + end + + context "when there is a cookbook in cookbooks1 and a cookbook in cookbooks2 with the same name" do + before do + file "cookbooks/blah/metadata.json", {} + file "cookbooks2/blah/metadata.rb", "" + end + it "knife list -Rfp cookbooks shows files in the first cookbook and not the second" do + knife("list --local -Rfp /cookbooks").should_succeed(<<~EOM, stderr: "WARN: Child with name 'blah' found in multiple directories: #{Chef::Config.cookbook_path[0]}/blah and #{Chef::Config.cookbook_path[1]}/blah\n") + /cookbooks/blah/ + /cookbooks/blah/metadata.json + /cookbooks/cookbook1/ + /cookbooks/cookbook1/metadata.rb + /cookbooks/cookbook2/ + /cookbooks/cookbook2/metadata.rb + EOM + end + end + + context "when there is a file in data_bags1 and a directory in data_bags2 with the same name" do + before do + file "data_bags/blah", "" + file "data_bags2/blah/item.json", "" + end + it "knife list -Rfp data_bags shows files in blah" do + knife("list --local -Rfp /data_bags").should_succeed <<~EOM + /data_bags/bag/ + /data_bags/bag/item.json + /data_bags/bag2/ + /data_bags/bag2/item2.json + /data_bags/blah/ + /data_bags/blah/item.json + EOM + end + end + + context "when there is a data bag in data_bags1 and a data bag in data_bags2 with the same name" do + before do + file "data_bags/blah/item1.json", "" + file "data_bags2/blah/item2.json", "" + end + it "knife list -Rfp data_bags shows only items in data_bags1" do + knife("list --local -Rfp /data_bags").should_succeed(<<~EOM, stderr: "WARN: Child with name 'blah' found in multiple directories: #{Chef::Config.data_bag_path[0]}/blah and #{Chef::Config.data_bag_path[1]}/blah\n") + /data_bags/bag/ + /data_bags/bag/item.json + /data_bags/bag2/ + /data_bags/bag2/item2.json + /data_bags/blah/ + /data_bags/blah/item1.json + EOM + end + end + + context "when there is a directory in environments1 and file in environments2 with the same name" do + before do + directory "environments/blah.json" + file "environments2/blah.json", {} + end + it "knife show /environments/blah.json succeeds" do + knife("show --local /environments/blah.json").should_succeed <<~EOM + /environments/blah.json: + { + + } + EOM + end + end + + context "when there is a directory in nodes1 and file in nodes2 with the same name" do + before do + directory "nodes/blah.json" + file "nodes2/blah.json", {} + end + it "knife show /nodes/blah.json succeeds" do + knife("show --local /nodes/blah.json").should_succeed <<~EOM + /nodes/blah.json: + { + + } + EOM + end + end + + context "when there is a directory in roles1 and file in roles2 with the same name" do + before do + directory "roles/blah.json" + file "roles2/blah.json", {} + end + it "knife show /roles/blah.json succeeds" do + knife("show --local /roles/blah.json").should_succeed <<~EOM + /roles/blah.json: + { + + } + EOM + end + end + + context "when there is a directory in users1 and file in users2 with the same name" do + before do + directory "users/blah.json" + file "users2/blah.json", {} + end + it "knife show /users/blah.json succeeds" do + knife("show --local /users/blah.json").should_succeed <<~EOM + /users/blah.json: + { + + } + EOM + end + end + + context "when cwd is at the top level" do + before { cwd "." } + it "knife list --local -Rfp fails" do + knife("list --local -Rfp").should_fail(error_rel_path_outside_repo) + end + end + + context "when cwd is inside the data_bags directory" do + before { cwd "data_bags" } + it "knife list --local -Rfp lists data bags" do + knife("list --local -Rfp").should_succeed <<~EOM + bag/ + bag/item.json + bag2/ + bag2/item2.json + EOM + end + end + + context "when cwd is inside chef_repo2" do + before { cwd "chef_repo2" } + it "knife list --local -Rfp lists everything" do + knife("list --local -Rfp").should_succeed <<~EOM + clients/ + clients/client1.json + clients/client2.json + cookbooks/ + cookbooks/cookbook1/ + cookbooks/cookbook1/metadata.rb + cookbooks/cookbook2/ + cookbooks/cookbook2/metadata.rb + data_bags/ + data_bags/bag/ + data_bags/bag/item.json + data_bags/bag2/ + data_bags/bag2/item2.json + environments/ + environments/env1.json + environments/env2.json + nodes/ + nodes/node1.json + nodes/node2.json + roles/ + roles/role1.json + roles/role2.json + users/ + users/user1.json + users/user2.json + EOM + end + end + + context "when cwd is inside data_bags2" do + before { cwd "data_bags2" } + it "knife list --local -Rfp lists data bags" do + knife("list --local -Rfp").should_succeed <<~EOM + bag/ + bag/item.json + bag2/ + bag2/item2.json + EOM + end + end + end + + context "when when chef_repo_path is set to both places and no other _path is set" do + before :each do + %w{client cookbook data_bag environment node role user}.each do |object_name| + Chef::Config.delete("#{object_name}_path".to_sym) + end + Chef::Config.chef_repo_path = [ + Chef::Config.chef_repo_path, + File.join(Chef::Config.chef_repo_path, "chef_repo2"), + ] + end + + context "when cwd is at the top level" do + before { cwd "." } + it "knife list --local -Rfp lists everything" do + knife("list --local -Rfp").should_succeed <<~EOM + clients/ + clients/client1.json + clients/client3.json + cookbooks/ + cookbooks/cookbook1/ + cookbooks/cookbook1/metadata.rb + cookbooks/cookbook3/ + cookbooks/cookbook3/metadata.rb + data_bags/ + data_bags/bag/ + data_bags/bag/item.json + data_bags/bag3/ + data_bags/bag3/item3.json + environments/ + environments/env1.json + environments/env3.json + nodes/ + nodes/node1.json + nodes/node3.json + roles/ + roles/role1.json + roles/role3.json + users/ + users/user1.json + users/user3.json + EOM + end + end + + context "when cwd is inside the data_bags directory" do + before { cwd "data_bags" } + it "knife list --local -Rfp lists data bags" do + knife("list --local -Rfp").should_succeed <<~EOM + bag/ + bag/item.json + bag3/ + bag3/item3.json + EOM + end + end + + context "when cwd is inside chef_repo2" do + before { cwd "chef_repo2" } + it "knife list --local -Rfp lists everything" do + knife("list --local -Rfp").should_succeed <<~EOM + clients/ + clients/client1.json + clients/client3.json + cookbooks/ + cookbooks/cookbook1/ + cookbooks/cookbook1/metadata.rb + cookbooks/cookbook3/ + cookbooks/cookbook3/metadata.rb + data_bags/ + data_bags/bag/ + data_bags/bag/item.json + data_bags/bag3/ + data_bags/bag3/item3.json + environments/ + environments/env1.json + environments/env3.json + nodes/ + nodes/node1.json + nodes/node3.json + roles/ + roles/role1.json + roles/role3.json + users/ + users/user1.json + users/user3.json + EOM + end + end + + context "when cwd is inside chef_repo2/data_bags" do + before { cwd "chef_repo2/data_bags" } + it "knife list --local -Rfp lists data bags" do + knife("list --local -Rfp").should_succeed <<~EOM + bag/ + bag/item.json + bag3/ + bag3/item3.json + EOM + end + end + end + + context "when cookbook_path is set and nothing else" do + before :each do + %w{client data_bag environment node role user}.each do |object_name| + Chef::Config.delete("#{object_name}_path".to_sym) + end + Chef::Config.delete(:chef_repo_path) + Chef::Config.cookbook_path = File.join(@repository_dir, "chef_repo2", "cookbooks") + end + + context "when cwd is at the top level" do + before { cwd "." } + it "knife list --local -Rfp fails" do + knife("list --local -Rfp").should_fail(error_rel_path_outside_repo) + end + end + + context "when cwd is inside the data_bags directory" do + before { cwd "data_bags" } + it "knife list --local -Rfp fails" do + knife("list --local -Rfp").should_fail(error_rel_path_outside_repo) + end + end + + context "when cwd is inside chef_repo2" do + before { cwd "chef_repo2" } + it "knife list --local -Rfp lists everything" do + knife("list --local -Rfp").should_succeed <<~EOM + clients/ + clients/client3.json + cookbooks/ + cookbooks/cookbook3/ + cookbooks/cookbook3/metadata.rb + data_bags/ + data_bags/bag3/ + data_bags/bag3/item3.json + environments/ + environments/env3.json + nodes/ + nodes/node3.json + roles/ + roles/role3.json + users/ + users/user3.json + EOM + end + end + + context "when cwd is inside chef_repo2/data_bags" do + before { cwd "chef_repo2/data_bags" } + it "knife list --local -Rfp lists data bags" do + knife("list --local -Rfp").should_succeed <<~EOM + bag3/ + bag3/item3.json + EOM + end + end + end + + context "when cookbook_path is set to multiple places and nothing else is set" do + before :each do + %w{client data_bag environment node role user}.each do |object_name| + Chef::Config.delete("#{object_name}_path".to_sym) + end + Chef::Config.delete(:chef_repo_path) + Chef::Config.cookbook_path = [ + File.join(@repository_dir, "cookbooks"), + File.join(@repository_dir, "chef_repo2", "cookbooks"), + ] + end + + context "when cwd is at the top level" do + before { cwd "." } + it "knife list --local -Rfp lists everything" do + knife("list --local -Rfp").should_succeed <<~EOM + clients/ + clients/client1.json + clients/client3.json + cookbooks/ + cookbooks/cookbook1/ + cookbooks/cookbook1/metadata.rb + cookbooks/cookbook3/ + cookbooks/cookbook3/metadata.rb + data_bags/ + data_bags/bag/ + data_bags/bag/item.json + data_bags/bag3/ + data_bags/bag3/item3.json + environments/ + environments/env1.json + environments/env3.json + nodes/ + nodes/node1.json + nodes/node3.json + roles/ + roles/role1.json + roles/role3.json + users/ + users/user1.json + users/user3.json + EOM + end + end + + context "when cwd is inside the data_bags directory" do + before { cwd "data_bags" } + it "knife list --local -Rfp lists data bags" do + knife("list --local -Rfp").should_succeed <<~EOM + bag/ + bag/item.json + bag3/ + bag3/item3.json + EOM + end + end + + context "when cwd is inside chef_repo2" do + before { cwd "chef_repo2" } + it "knife list --local -Rfp lists everything" do + knife("list --local -Rfp").should_succeed <<~EOM + clients/ + clients/client1.json + clients/client3.json + cookbooks/ + cookbooks/cookbook1/ + cookbooks/cookbook1/metadata.rb + cookbooks/cookbook3/ + cookbooks/cookbook3/metadata.rb + data_bags/ + data_bags/bag/ + data_bags/bag/item.json + data_bags/bag3/ + data_bags/bag3/item3.json + environments/ + environments/env1.json + environments/env3.json + nodes/ + nodes/node1.json + nodes/node3.json + roles/ + roles/role1.json + roles/role3.json + users/ + users/user1.json + users/user3.json + EOM + end + end + + context "when cwd is inside chef_repo2/data_bags" do + before { cwd "chef_repo2/data_bags" } + it "knife list --local -Rfp lists data bags" do + knife("list --local -Rfp").should_succeed <<~EOM + bag/ + bag/item.json + bag3/ + bag3/item3.json + EOM + end + end + end + + context "when data_bag_path and chef_repo_path are set, and nothing else" do + before :each do + %w{client cookbook environment node role user}.each do |object_name| + Chef::Config.delete("#{object_name}_path".to_sym) + end + Chef::Config.data_bag_path = File.join(Chef::Config.chef_repo_path, "data_bags") + Chef::Config.chef_repo_path = File.join(Chef::Config.chef_repo_path, "chef_repo2") + end + + context "when cwd is at the top level" do + before { cwd "." } + it "knife list --local -Rfp fails" do + knife("list --local -Rfp").should_fail(error_rel_path_outside_repo) + end + end + + context "when cwd is inside the data_bags directory" do + before { cwd "data_bags" } + it "knife list --local -Rfp lists data bags" do + knife("list --local -Rfp").should_succeed <<~EOM + bag/ + bag/item.json + EOM + end + end + + context "when cwd is inside chef_repo2" do + before { cwd "chef_repo2" } + it "knife list --local -Rfp lists everything" do + knife("list --local -Rfp").should_succeed <<~EOM + clients/ + clients/client3.json + cookbooks/ + cookbooks/cookbook3/ + cookbooks/cookbook3/metadata.rb + data_bags/ + data_bags/bag/ + data_bags/bag/item.json + environments/ + environments/env3.json + nodes/ + nodes/node3.json + roles/ + roles/role3.json + users/ + users/user3.json + EOM + end + end + + context "when cwd is inside chef_repo2/data_bags" do + before { cwd "chef_repo2/data_bags" } + it "knife list --local -Rfp fails" do + knife("list --local -Rfp").should_fail(error_rel_path_outside_repo) + end + end + end + + context "when data_bag_path is set and nothing else" do + include_context "default config options" + + before :each do + %w{client cookbook environment node role user}.each do |object_name| + Chef::Config.delete("#{object_name}_path".to_sym) + end + Chef::Config.delete(:chef_repo_path) + Chef::Config.data_bag_path = File.join(@repository_dir, "data_bags") + end + + it "knife list --local -Rfp / lists data bags" do + knife("list --local -Rfp /").should_succeed <<~EOM + /data_bags/ + /data_bags/bag/ + /data_bags/bag/item.json + EOM + end + + it "knife list --local -Rfp /data_bags lists data bags" do + knife("list --local -Rfp /data_bags").should_succeed <<~EOM + /data_bags/bag/ + /data_bags/bag/item.json + EOM + end + + context "when cwd is inside the data_bags directory" do + before { cwd "data_bags" } + it "knife list --local -Rfp lists data bags" do + knife("list --local -Rfp").should_succeed <<~EOM + bag/ + bag/item.json + EOM + end + end + end + end + + when_the_repository "is empty" do + context "when the repository _paths point to places that do not exist" do + before :each do + %w{client cookbook data_bag environment node role user}.each do |object_name| + Chef::Config["#{object_name}_path".to_sym] = File.join(Chef::Config.chef_repo_path, "nowhere", object_name) + end + Chef::Config.chef_repo_path = File.join(Chef::Config.chef_repo_path, "nowhere") + end + + it "knife list --local -Rfp / fails" do + knife("list --local -Rfp /").should_succeed "" + end + + it "knife list --local -Rfp /data_bags fails" do + knife("list --local -Rfp /data_bags").should_fail("ERROR: /data_bags: No such file or directory\n") + end + end + end + end +end diff --git a/knife/spec/integration/chef_repository_file_system_spec.rb b/knife/spec/integration/chef_repository_file_system_spec.rb new file mode 100644 index 0000000000..9a129dcb98 --- /dev/null +++ b/knife/spec/integration/chef_repository_file_system_spec.rb @@ -0,0 +1,200 @@ +# +# 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/list" +require "chef/knife/show" + +describe "General chef_repo file system checks", :workstation do + include IntegrationSupport + include KnifeSupport + + context "directories and files that should/should not be ignored" do + when_the_repository "has empty roles, environments and data bag item directories" do + before do + directory "roles" + directory "environments" + directory "data_bags/bag1" + end + + it "knife list --local -Rfp / returns them" do + knife("list --local -Rfp /").should_succeed <<~EOM + /data_bags/ + /data_bags/bag1/ + /environments/ + /roles/ + EOM + end + end + + when_the_repository "has an empty data_bags directory" do + before { directory "data_bags" } + + it "knife list --local / returns it" do + knife("list --local /").should_succeed "/data_bags\n" + end + end + + when_the_repository "has an empty cookbook directory" do + before { directory "cookbooks/cookbook1" } + + it "knife list --local -Rfp / does not return it" do + knife("list --local -Rfp /").should_succeed(<<~EOM, stderr: "WARN: Cookbook 'cookbook1' is empty or entirely chefignored at #{Chef::Config.chef_repo_path}/cookbooks/cookbook1\n") + /cookbooks/ + EOM + end + end + + when_the_repository "has only empty cookbook subdirectories" do + before { directory "cookbooks/cookbook1/recipes" } + + it "knife list --local -Rfp / does not return it" do + knife("list --local -Rfp /").should_succeed(<<~EOM, stderr: "WARN: Cookbook 'cookbook1' is empty or entirely chefignored at #{Chef::Config.chef_repo_path}/cookbooks/cookbook1\n") + /cookbooks/ + EOM + end + end + + when_the_repository "has empty and non-empty cookbook subdirectories" do + before do + directory "cookbooks/cookbook1/recipes" + file "cookbooks/cookbook1/templates/default/x.txt", "" + end + + it "knife list --local -Rfp / does not return the empty ones" do + knife("list --local -Rfp /").should_succeed <<~EOM + /cookbooks/ + /cookbooks/cookbook1/ + /cookbooks/cookbook1/templates/ + /cookbooks/cookbook1/templates/default/ + /cookbooks/cookbook1/templates/default/x.txt + EOM + end + end + + when_the_repository "has only empty cookbook sub-sub-directories" do + before { directory "cookbooks/cookbook1/templates/default" } + + it "knife list --local -Rfp / does not return it" do + knife("list --local -Rfp /").should_succeed(<<~EOM, stderr: "WARN: Cookbook 'cookbook1' is empty or entirely chefignored at #{Chef::Config.chef_repo_path}/cookbooks/cookbook1\n") + /cookbooks/ + EOM + end + end + + when_the_repository "has empty cookbook sub-sub-directories alongside non-empty ones" do + before do + file "cookbooks/cookbook1/templates/default/x.txt", "" + directory "cookbooks/cookbook1/templates/rhel" + directory "cookbooks/cookbook1/files/default" + end + + it "knife list --local -Rfp / does not return the empty ones" do + knife("list --local -Rfp /").should_succeed <<~EOM + /cookbooks/ + /cookbooks/cookbook1/ + /cookbooks/cookbook1/templates/ + /cookbooks/cookbook1/templates/default/ + /cookbooks/cookbook1/templates/default/x.txt + EOM + end + end + + when_the_repository "has an extra schmenvironments directory" do + before do + directory "schmenvironments" do + file "_default.json", {} + end + end + + it "knife list --local -Rfp / should NOT return it" do + knife("list --local -Rfp /").should_succeed "" + end + end + + when_the_repository "has extra subdirectories and files under data bag items, roles, and environments" do + before do + directory "data_bags/bag1" do + file "item1.json", {} + file "item2.xml", "" + file "another_subdir/item.json", {} + end + directory "roles" do + file "role1.json", {} + file "role2.xml", "" + file "subdir/role.json", {} + end + directory "environments" do + file "environment1.json", {} + file "environment2.xml", "" + file "subdir/environment.json", {} + end + end + + it "knife list --local -Rfp / should NOT return them" do + knife("list --local -Rfp /").should_succeed <<~EOM + /data_bags/ + /data_bags/bag1/ + /data_bags/bag1/item1.json + /environments/ + /environments/environment1.json + /roles/ + /roles/role1.json + EOM + end + end + + when_the_repository "has a file in cookbooks/" do + before { file "cookbooks/file", "" } + it "does not show up in list -Rfp" do + knife("list --local -Rfp /").should_succeed <<~EOM + /cookbooks/ + EOM + end + end + + when_the_repository "has a file in data_bags/" do + before { file "data_bags/file", "" } + it "does not show up in list -Rfp" do + knife("list --local -Rfp /").should_succeed <<~EOM + /data_bags/ + EOM + end + end + end + + when_the_repository "has a cookbook starting with ." do + before do + file "cookbooks/.svn/metadata.rb", "" + file "cookbooks/a.b/metadata.rb", "" + end + it "knife list does not show it" do + knife("list --local -fp /cookbooks").should_succeed "/cookbooks/a.b/\n" + end + end + + when_the_repository "has a data bag starting with ." do + before do + file "data_bags/.svn/x.json", {} + file "data_bags/a.b/x.json", {} + end + it "knife list does not show it" do + knife("list --local -fp /data_bags").should_succeed "/data_bags/a.b/\n" + end + end +end diff --git a/knife/spec/integration/chefignore_spec.rb b/knife/spec/integration/chefignore_spec.rb new file mode 100644 index 0000000000..f111cd56e1 --- /dev/null +++ b/knife/spec/integration/chefignore_spec.rb @@ -0,0 +1,301 @@ +# +# 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/list" +require "chef/knife/show" + +describe "chefignore tests", :workstation do + include IntegrationSupport + include KnifeSupport + + when_the_repository "has lots of stuff in it" do + before do + file "roles/x.json", {} + file "environments/x.json", {} + file "data_bags/bag1/x.json", {} + file "cookbooks/cookbook1/x.json", {} + end + + context "and has a chefignore everywhere except cookbooks" do + before do + chefignore = "x.json\nroles/x.json\nenvironments/x.json\ndata_bags/bag1/x.json\nbag1/x.json\ncookbooks/cookbook1/x.json\ncookbook1/x.json\n" + file "chefignore", chefignore + file "roles/chefignore", chefignore + file "environments/chefignore", chefignore + file "data_bags/chefignore", chefignore + file "data_bags/bag1/chefignore", chefignore + file "cookbooks/cookbook1/chefignore", chefignore + end + + it "matching files and directories get ignored" do + # NOTE: many of the "chefignore" files should probably not show up + # themselves, but we have other tests that talk about that + knife("list --local -Rfp /").should_succeed <<~EOM + /cookbooks/ + /cookbooks/cookbook1/ + /cookbooks/cookbook1/chefignore + /data_bags/ + /data_bags/bag1/ + /data_bags/bag1/x.json + /environments/ + /environments/x.json + /roles/ + /roles/x.json + EOM + end + end + end + + when_the_repository "has a cookbook with only chefignored files" do + before do + file "cookbooks/cookbook1/templates/default/x.rb", "" + file "cookbooks/cookbook1/libraries/x.rb", "" + file "cookbooks/chefignore", "libraries/x.rb\ntemplates/default/x.rb\n" + end + + it "the cookbook is not listed" do + knife("list --local -Rfp /").should_succeed(<<~EOM, stderr: "WARN: Cookbook 'cookbook1' is empty or entirely chefignored at #{Chef::Config.chef_repo_path}/cookbooks/cookbook1\n") + /cookbooks/ + EOM + end + end + + when_the_repository "has multiple cookbooks" do + before do + file "cookbooks/cookbook1/x.json", {} + file "cookbooks/cookbook1/y.json", {} + file "cookbooks/cookbook2/x.json", {} + file "cookbooks/cookbook2/y.json", {} + end + + context "and has a chefignore with filenames" do + before { file "cookbooks/chefignore", "x.json\n" } + + it "matching files and directories get ignored in all cookbooks" do + knife("list --local -Rfp /").should_succeed <<~EOM + /cookbooks/ + /cookbooks/cookbook1/ + /cookbooks/cookbook1/y.json + /cookbooks/cookbook2/ + /cookbooks/cookbook2/y.json + EOM + end + end + + context "and has a chefignore with wildcards" do + before do + file "cookbooks/chefignore", "x.*\n" + file "cookbooks/cookbook1/x.rb", "" + end + + it "matching files and directories get ignored in all cookbooks" do + knife("list --local -Rfp /").should_succeed <<~EOM + /cookbooks/ + /cookbooks/cookbook1/ + /cookbooks/cookbook1/y.json + /cookbooks/cookbook2/ + /cookbooks/cookbook2/y.json + EOM + end + end + + context "and has a chefignore with relative paths" do + before do + file "cookbooks/cookbook1/recipes/x.rb", "" + file "cookbooks/cookbook2/recipes/y.rb", "" + file "cookbooks/chefignore", "recipes/x.rb\n" + end + + it "matching directories get ignored" do + knife("list --local -Rfp /").should_succeed <<~EOM + /cookbooks/ + /cookbooks/cookbook1/ + /cookbooks/cookbook1/x.json + /cookbooks/cookbook1/y.json + /cookbooks/cookbook2/ + /cookbooks/cookbook2/recipes/ + /cookbooks/cookbook2/recipes/y.rb + /cookbooks/cookbook2/x.json + /cookbooks/cookbook2/y.json + EOM + end + end + + context "and has a chefignore with subdirectories" do + before do + file "cookbooks/cookbook1/recipes/y.rb", "" + file "cookbooks/chefignore", "recipes\nrecipes/\n" + end + + it "matching directories do NOT get ignored" do + knife("list --local -Rfp /").should_succeed <<~EOM + /cookbooks/ + /cookbooks/cookbook1/ + /cookbooks/cookbook1/recipes/ + /cookbooks/cookbook1/recipes/y.rb + /cookbooks/cookbook1/x.json + /cookbooks/cookbook1/y.json + /cookbooks/cookbook2/ + /cookbooks/cookbook2/x.json + /cookbooks/cookbook2/y.json + EOM + end + end + + context "and has a chefignore that ignores all files in a subdirectory" do + before do + file "cookbooks/cookbook1/templates/default/x.rb", "" + file "cookbooks/cookbook1/libraries/x.rb", "" + file "cookbooks/chefignore", "libraries/x.rb\ntemplates/default/x.rb\n" + end + + it "ignores the subdirectory entirely" do + knife("list --local -Rfp /").should_succeed <<~EOM + /cookbooks/ + /cookbooks/cookbook1/ + /cookbooks/cookbook1/x.json + /cookbooks/cookbook1/y.json + /cookbooks/cookbook2/ + /cookbooks/cookbook2/x.json + /cookbooks/cookbook2/y.json + EOM + end + end + + context "and has an empty chefignore" do + before do + file "cookbooks/chefignore", "\n" + end + + it "nothing is ignored" do + knife("list --local -Rfp /").should_succeed <<~EOM + /cookbooks/ + /cookbooks/cookbook1/ + /cookbooks/cookbook1/x.json + /cookbooks/cookbook1/y.json + /cookbooks/cookbook2/ + /cookbooks/cookbook2/x.json + /cookbooks/cookbook2/y.json + EOM + end + end + + context "and has a chefignore with comments and empty lines" do + before do + file "cookbooks/chefignore", "\n\n # blah\n#\nx.json\n\n" + end + + it "matching files and directories get ignored in all cookbooks" do + knife("list --local -Rfp /").should_succeed <<~EOM + /cookbooks/ + /cookbooks/cookbook1/ + /cookbooks/cookbook1/y.json + /cookbooks/cookbook2/ + /cookbooks/cookbook2/y.json + EOM + end + end + end + + when_the_repository "has multiple cookbook paths" do + before :each do + Chef::Config.cookbook_path = [ + File.join(Chef::Config.chef_repo_path, "cookbooks1"), + File.join(Chef::Config.chef_repo_path, "cookbooks2"), + ] + end + + before do + file "cookbooks1/mycookbook/metadata.rb", "" + file "cookbooks1/mycookbook/x.json", {} + file "cookbooks2/yourcookbook/metadata.rb", "" + file "cookbooks2/yourcookbook/x.json", "" + end + + context "and multiple chefignores" do + before do + file "cookbooks1/chefignore", "metadata.rb\n" + file "cookbooks2/chefignore", "x.json\n" + end + it "chefignores apply only to the directories they are in" do + knife("list --local -Rfp /").should_succeed <<~EOM + /cookbooks/ + /cookbooks/mycookbook/ + /cookbooks/mycookbook/x.json + /cookbooks/yourcookbook/ + /cookbooks/yourcookbook/metadata.rb + EOM + end + + context "and conflicting cookbooks" do + before do + file "cookbooks1/yourcookbook/metadata.rb", "" + file "cookbooks1/yourcookbook/x.json", "" + file "cookbooks1/yourcookbook/onlyincookbooks1.rb", "" + file "cookbooks2/yourcookbook/onlyincookbooks2.rb", "" + end + + it "chefignores apply only to the winning cookbook" do + knife("list --local -Rfp /").should_succeed(<<~EOM, stderr: "WARN: Child with name 'yourcookbook' found in multiple directories: #{Chef::Config.chef_repo_path}/cookbooks1/yourcookbook and #{Chef::Config.chef_repo_path}/cookbooks2/yourcookbook\n") + /cookbooks/ + /cookbooks/mycookbook/ + /cookbooks/mycookbook/x.json + /cookbooks/yourcookbook/ + /cookbooks/yourcookbook/onlyincookbooks1.rb + /cookbooks/yourcookbook/x.json + EOM + end + end + end + end + + when_the_repository "has a cookbook named chefignore" do + before do + file "cookbooks/chefignore/metadata.rb", {} + end + it "knife list -Rfp /cookbooks shows it" do + knife("list --local -Rfp /cookbooks").should_succeed <<~EOM + /cookbooks/chefignore/ + /cookbooks/chefignore/metadata.rb + EOM + end + end + + when_the_repository "has multiple cookbook paths, one with a chefignore file and the other with a cookbook named chefignore" do + before do + file "cookbooks1/chefignore", "" + file "cookbooks1/blah/metadata.rb", "" + file "cookbooks2/chefignore/metadata.rb", "" + end + before :each do + Chef::Config.cookbook_path = [ + File.join(Chef::Config.chef_repo_path, "cookbooks1"), + File.join(Chef::Config.chef_repo_path, "cookbooks2"), + ] + end + it "knife list -Rfp /cookbooks shows the chefignore cookbook" do + knife("list --local -Rfp /cookbooks").should_succeed <<~EOM + /cookbooks/blah/ + /cookbooks/blah/metadata.rb + /cookbooks/chefignore/ + /cookbooks/chefignore/metadata.rb + EOM + end + end +end diff --git a/knife/spec/integration/client_bulk_delete_spec.rb b/knife/spec/integration/client_bulk_delete_spec.rb new file mode 100644 index 0000000000..b7733f638d --- /dev/null +++ b/knife/spec/integration/client_bulk_delete_spec.rb @@ -0,0 +1,131 @@ +# +# 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 "support/shared/context/config" + +describe "knife client bulk delete", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has some clients" do + before do + client "concat", {} + client "cons", {} + client "car", {} + client "cdr", {} + client "cat", {} + end + + it "deletes all matching clients" do + knife("client bulk delete ^ca.*", input: "Y").should_succeed <<~EOM + The following clients will be deleted: + + car cat + + Are you sure you want to delete these clients? (Y/N) Deleted client car + Deleted client cat + EOM + + knife("client list").should_succeed <<~EOM + cdr + chef-validator + chef-webui + concat + cons + EOM + end + + it "deletes all matching clients when unanchored" do + knife("client bulk delete ca.*", input: "Y").should_succeed <<~EOM + The following clients will be deleted: + + car cat concat + + Are you sure you want to delete these clients? (Y/N) Deleted client car + Deleted client cat + Deleted client concat + EOM + + knife("client list").should_succeed <<~EOM + cdr + chef-validator + chef-webui + cons + EOM + end + end + + when_the_chef_server "has a validator client" do + before do + client "cons", {} + client "car", {} + client "car-validator", { validator: true } + client "cdr", {} + client "cat", {} + end + + it "refuses to delete a validator normally" do + knife("client bulk delete ^ca.*", input: "Y").should_succeed <<~EOM + The following clients are validators and will not be deleted: + + car-validator + + You must specify --delete-validators to delete the validator clients + The following clients will be deleted: + + car cat + + Are you sure you want to delete these clients? (Y/N) Deleted client car + Deleted client cat + EOM + + knife("client list").should_succeed <<~EOM + car-validator + cdr + chef-validator + chef-webui + cons + EOM + end + + it "deletes a validator when told to" do + knife("client bulk delete ^ca.* -D", input: "Y\nY").should_succeed <<~EOM + The following validators will be deleted: + + car-validator + + Are you sure you want to delete these validators? (Y/N) Deleted client car-validator + The following clients will be deleted: + + car cat + + Are you sure you want to delete these clients? (Y/N) Deleted client car + Deleted client cat + EOM + + knife("client list").should_succeed <<~EOM + cdr + chef-validator + chef-webui + cons + EOM + end + end +end diff --git a/knife/spec/integration/client_create_spec.rb b/knife/spec/integration/client_create_spec.rb new file mode 100644 index 0000000000..3898ff9d24 --- /dev/null +++ b/knife/spec/integration/client_create_spec.rb @@ -0,0 +1,70 @@ +# +# 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 "support/shared/context/config" +require "openssl" + +describe "knife client create", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + let(:out) { "Created client[bah]\n" } + + when_the_chef_server "is empty" do + it "creates a new client" do + knife("client create -k bah").should_succeed stderr: out + end + + it "creates a new validator client" do + knife("client create -k --validator bah").should_succeed stderr: out + knife("client show bah").should_succeed <<~EOM + admin: false + chef_type: client + name: bah + validator: true + EOM + end + + it "refuses to add an existing client" do + pending "Knife client create must not blindly overwrite an existing client" + knife("client create -k bah").should_succeed stderr: out + expect { knife("client create -k bah") }.to raise_error(Net::HTTPClientException) + end + + it "saves the private key to a file" do + Dir.mktmpdir do |tgt| + knife("client create -f #{tgt}/bah.pem bah").should_succeed stderr: out + expect(File).to exist("#{tgt}/bah.pem") + end + end + + it "reads the public key from a file" do + Dir.mktmpdir do |tgt| + key = OpenSSL::PKey::RSA.generate(1024) + File.open("#{tgt}/public.pem", "w") { |pub| pub.write(key.public_key.to_pem) } + knife("client create -p #{tgt}/public.pem bah").should_succeed stderr: out + end + end + + it "refuses to run if conflicting options are passed" do + knife("client create -p public.pem --prevent-keygen blah").should_fail stderr: "FATAL: You cannot pass --public-key and --prevent-keygen\n", stdout: /^USAGE.*/ + end + end +end diff --git a/knife/spec/integration/client_delete_spec.rb b/knife/spec/integration/client_delete_spec.rb new file mode 100644 index 0000000000..057561eaea --- /dev/null +++ b/knife/spec/integration/client_delete_spec.rb @@ -0,0 +1,64 @@ +# +# 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 "support/shared/context/config" + +describe "knife client delete", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has some clients" do + before do + client "cons", {} + client "car", {} + client "car-validator", { validator: true } + client "cdr", {} + client "cat", {} + end + + it "deletes a client" do + knife("client delete car", input: "Y").should_succeed <<~EOM + Do you really want to delete car? (Y/N) Deleted client[car] + EOM + + knife("client list").should_succeed <<~EOM + car-validator + cat + cdr + chef-validator + chef-webui + cons + EOM + end + + it "refuses to delete a validator normally" do + knife("client delete car-validator", input: "Y").should_fail exit_code: 2, stdout: "Do you really want to delete car-validator? (Y/N) ", stderr: <<~EOM + FATAL: You must specify --delete-validators to delete the validator client car-validator + EOM + end + + it "deletes a validator correctly" do + knife("client delete car-validator -D", input: "Y").should_succeed <<~EOM + Do you really want to delete car-validator? (Y/N) Deleted client[car-validator] + EOM + end + + end +end diff --git a/knife/spec/integration/client_key_create_spec.rb b/knife/spec/integration/client_key_create_spec.rb new file mode 100644 index 0000000000..29b960111c --- /dev/null +++ b/knife/spec/integration/client_key_create_spec.rb @@ -0,0 +1,66 @@ +# +# 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 "support/shared/context/config" +require "openssl" + +describe "knife client key create", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + let(:out) { "Created key: new" } + + when_the_chef_server "has a client" do + before do + client "bah", {} + end + + it "creates a new client key" do + knife("client key create -k new bah").should_succeed stderr: /^#{out}/, stdout: /.*BEGIN RSA PRIVATE KEY/ + end + + it "creates a new client key with an expiration date" do + date = "2017-12-31T23:59:59Z" + knife("client key create -k new -e #{date} bah").should_succeed stderr: /^#{out}/, stdout: /.*BEGIN RSA PRIVATE KEY/ + knife("client key show bah new").should_succeed(/expiration_date:.*#{date}/) + end + + it "refuses to add an already existing key" do + knife("client key create -k new bah") + expect { knife("client key create -k new bah") }.to raise_error(Net::HTTPClientException) + end + + it "saves the private key to a file" do + Dir.mktmpdir do |tgt| + knife("client key create -f #{tgt}/bah.pem -k new bah").should_succeed stderr: /^#{out}/ + expect(File).to exist("#{tgt}/bah.pem") + end + end + + it "reads the public key from a file" do + Dir.mktmpdir do |tgt| + key = OpenSSL::PKey::RSA.generate(1024) + File.open("#{tgt}/public.pem", "w") { |pub| pub.write(key.public_key.to_pem) } + knife("client key create -p #{tgt}/public.pem -k new bah").should_succeed stderr: /^#{out}/ + end + end + + end +end diff --git a/knife/spec/integration/client_key_delete_spec.rb b/knife/spec/integration/client_key_delete_spec.rb new file mode 100644 index 0000000000..8c15377986 --- /dev/null +++ b/knife/spec/integration/client_key_delete_spec.rb @@ -0,0 +1,43 @@ +# +# 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 "support/shared/context/config" + +describe "knife client key delete", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has a client" do + before do + client "car", {} + end + + it "deletes a client" do + out = "Do you really want to delete the key named new for the client named car? (Y/N) " + knife("client key create -k new car") + knife("client key delete car new", input: "Y").should_succeed stdout: out, stderr: <<~EOM + Deleted key named new for the client named car + EOM + + knife("client key list car").should_succeed "" + end + + end +end diff --git a/knife/spec/integration/client_key_list_spec.rb b/knife/spec/integration/client_key_list_spec.rb new file mode 100644 index 0000000000..01e5b78585 --- /dev/null +++ b/knife/spec/integration/client_key_list_spec.rb @@ -0,0 +1,61 @@ +# +# 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 "support/shared/context/config" +require "date" + +describe "knife client key list", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + let(:now) { DateTime.now } + let(:last_month) { (now << 1).strftime("%FT%TZ") } + let(:next_month) { (now >> 1).strftime("%FT%TZ") } + + when_the_chef_server "has a client" do + before do + client "cons", {} + knife("client key create cons -k new") + knife("client key create cons -k next_month -e #{next_month}") + knife("client key create cons -k expired -e #{last_month}") + end + + it "lists the keys for a client" do + knife("client key list cons").should_succeed "expired\nnew\nnext_month\n" + end + + it "shows detailed output" do + knife("client key list -w cons").should_succeed <<~EOM + expired: http://127.0.0.1:8900/clients/cons/keys/expired (expired) + new: http://127.0.0.1:8900/clients/cons/keys/new + next_month: http://127.0.0.1:8900/clients/cons/keys/next_month + EOM + end + + it "lists the expired keys for a client" do + knife("client key list -e cons").should_succeed "expired\n" + end + + it "lists the unexpired keys for a client" do + knife("client key list -n cons").should_succeed "new\nnext_month\n" + end + + end +end diff --git a/knife/spec/integration/client_key_show_spec.rb b/knife/spec/integration/client_key_show_spec.rb new file mode 100644 index 0000000000..05024d40b2 --- /dev/null +++ b/knife/spec/integration/client_key_show_spec.rb @@ -0,0 +1,45 @@ +# +# 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 "support/shared/context/config" +require "date" + +describe "knife client key show", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + let(:now) { DateTime.now } + let(:last_month) { (now << 1).strftime("%FT%TZ") } + let(:next_month) { (now >> 1).strftime("%FT%TZ") } + + when_the_chef_server "has a client" do + before do + client "cons", {} + knife("client key create cons -k new") + knife("client key create cons -k next_month -e #{next_month}") + knife("client key create cons -k expired -e #{last_month}") + end + + it "shows a key for a client" do + knife("client key show cons new").should_succeed stdout: /.*name:.*new/ + end + + end +end diff --git a/knife/spec/integration/client_list_spec.rb b/knife/spec/integration/client_list_spec.rb new file mode 100644 index 0000000000..7668b9e455 --- /dev/null +++ b/knife/spec/integration/client_list_spec.rb @@ -0,0 +1,49 @@ +# +# 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 "support/shared/context/config" + +describe "knife client list", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has some clients" do + before do + client "cons", {} + client "car", {} + client "car-validator", { validator: true } + client "cdr", {} + client "cat", {} + end + + it "lists the clients" do + knife("client list").should_succeed <<~EOM + car + car-validator + cat + cdr + chef-validator + chef-webui + cons + EOM + end + + end +end diff --git a/knife/spec/integration/client_show_spec.rb b/knife/spec/integration/client_show_spec.rb new file mode 100644 index 0000000000..39a107e37f --- /dev/null +++ b/knife/spec/integration/client_show_spec.rb @@ -0,0 +1,37 @@ +# +# 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 "support/shared/context/config" + +describe "knife client show", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has a client" do + before do + client "cons", {} + end + + it "shows a client" do + knife("client show cons").should_succeed stdout: /.*name:.*cons/ + end + + end +end diff --git a/knife/spec/integration/common_options_spec.rb b/knife/spec/integration/common_options_spec.rb new file mode 100644 index 0000000000..7796bf9923 --- /dev/null +++ b/knife/spec/integration/common_options_spec.rb @@ -0,0 +1,174 @@ +# +# 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/raw" + +describe "knife common options", :workstation do + include IntegrationSupport + include KnifeSupport + + before do + # Allow this for testing the various port binding stuffs. Remove when + # we kill off --listen. + Chef::Config.treat_deprecation_warnings_as_errors(false) + end + + let(:local_listen_warning) { /\Awarn:.*local.*listen.*$/im } + + when_the_repository "has a node" do + before { file "nodes/x.json", {} } + + context "When chef_zero.enabled is true" do + before(:each) do + Chef::Config.chef_zero.enabled = true + end + + it "knife raw /nodes/x should retrieve the node in socketless mode" do + Chef::Config.treat_deprecation_warnings_as_errors(true) + knife("raw /nodes/x").should_succeed( /"name": "x"/ ) + end + + it "knife raw /nodes/x should retrieve the node" do + knife("raw --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning ) + end + + context "And chef_zero.port is 9999" do + before(:each) { Chef::Config.chef_zero.port = 9999 } + + it "knife raw /nodes/x should retrieve the node" do + knife("raw --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning ) + expect(Chef::Config.chef_server_url).to eq("chefzero://localhost:9999") + end + end + + # 0.0.0.0 is not a valid address to bind to on windows. + context "And chef_zero.host is 0.0.0.0", :unix_only do + before(:each) { Chef::Config.chef_zero.host = "0.0.0.0" } + + it "knife raw /nodes/x should retrieve the role" do + knife("raw --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning ) + end + end + + context "and there is a private key" do + before do + file "mykey.pem", <<~EOM + -----BEGIN RSA PRIVATE KEY----- + MIIEogIBAAKCAQEApubutqtYYQ5UiA9QhWP7UvSmsfHsAoPKEVVPdVW/e8Svwpyf + 0Xef6OFWVmBE+W442ZjLOe2y6p2nSnaq4y7dg99NFz6X+16mcKiCbj0RCiGqCvCk + NftHhTgO9/RFvCbmKZ1RKNob1YzLrFpxBHaSh9po+DGWhApcd+I+op+ZzvDgXhNn + 0nauZu3rZmApI/r7EEAOjFedAXs7VPNXhhtZAiLSAVIrwU3ZajtSzgXOxbNzgj5O + AAAMmThK+71qPdffAdO4J198H6/MY04qgtFo7vumzCq0UCaGZfmeI1UNE4+xQWwP + HJ3pDAP61C6Ebx2snI2kAd9QMx9Y78nIedRHPwIDAQABAoIBAHssRtPM1GacWsom + 8zfeN6ZbI4KDlbetZz0vhnqDk9NVrpijWlcOP5dwZXVNitnB/HaqCqFvyPDY9JNB + zI/pEFW4QH59FVDP42mVEt0keCTP/1wfiDDGh1vLqVBYl/ZphscDcNgDTzNkuxMx + k+LFVxKnn3w7rGc59lALSkpeGvbbIDjp3LUMlUeCF8CIFyYZh9ZvXe4OCxYdyjxb + i8tnMLKvJ4Psbh5jMapsu3rHQkfPdqzztQUz8vs0NYwP5vWge46FUyk+WNm/IhbJ + G3YM22nwUS8Eu2bmTtADSJolATbCSkOwQ1D+Fybz/4obfYeGaCdOqB05ttubhenV + ShsAb7ECgYEA20ecRVxw2S7qA7sqJ4NuYOg9TpfGooptYNA1IP971eB6SaGAelEL + awYkGNuu2URmm5ElZpwJFFTDLGA7t2zB2xI1FeySPPIVPvJGSiZoFQOVlIg9WQzK + 7jTtFQ/tOMrF+bigEUJh5bP1/7HzqSpuOsPjEUb2aoCTp+tpiRGL7TUCgYEAwtns + g3ysrSEcTzpSv7fQRJRk1lkBhatgNd0oc+ikzf74DaVLhBg1jvSThDhiDCdB59mr + Jh41cnR1XqE8jmdQbCDRiFrI1Pq6TPaDZFcovDVE1gue9x86v3FOH2ukPG4d2/Xy + HevXjThtpMMsWFi0JYXuzXuV5HOvLZiP8sN3lSMCgYANpdxdGM7RRbE9ADY0dWK2 + V14ReTLcxP7fyrWz0xLzEeCqmomzkz3BsIUoouu0DCTSw+rvAwExqcDoDylIVlWO + fAifz7SeZHbcDxo+3TsXK7zwnLYsx7YNs2+aIv6hzUUbMNmNmXMcZ+IEwx+mRMTN + lYmZdrA5mr0V83oDFPt/jQKBgC74RVE03pMlZiObFZNtheDiPKSG9Bz6wMh7NWMr + c37MtZLkg52mEFMTlfPLe6ceV37CM8WOhqe+dwSGrYhOU06dYqUR7VOZ1Qr0aZvo + fsNPu/Y0+u7rMkgv0fs1AXQnvz7kvKaF0YITVirfeXMafuKEtJoH7owRbur42cpV + YCAtAoGAP1rHOc+w0RUcBK3sY7aErrih0OPh9U5bvJsrw1C0FIZhCEoDVA+fNIQL + syHLXYFNy0OxMtH/bBAXBGNHd9gf5uOnqh0pYcbe/uRAxumC7Rl0cL509eURiA2T + +vFmf54y9YdnLXaqv+FhJT6B6V7WX7IpU9BMqJY1cJYXHuHG2KA= + -----END RSA PRIVATE KEY----- + EOM + end + + it "knife raw /nodes/x should retrieve the node" do + knife("raw --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning ) + end + end + end + + it "knife raw -z /nodes/x retrieves the node in socketless mode" do + Chef::Config.treat_deprecation_warnings_as_errors(true) + knife("raw -z /nodes/x").should_succeed( /"name": "x"/ ) + end + + it "knife raw -z /nodes/x retrieves the node" do + knife("raw -z --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning ) + end + + it "knife raw --local-mode /nodes/x retrieves the node" do + knife("raw --local-mode --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning ) + end + + it "knife raw -z --chef-zero-port=9999 /nodes/x retrieves the node" do + knife("raw -z --chef-zero-port=9999 --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning ) + expect(Chef::Config.chef_server_url).to eq("chefzero://localhost:9999") + end + + context "when the default port (8889) is already bound" do + before :each do + + @server = ChefZero::Server.new(host: "localhost", port: 8889) + @server.start_background + rescue Errno::EADDRINUSE + # OK. Don't care who has it in use, as long as *someone* does. + + end + after :each do + @server.stop if @server + end + + it "knife raw -z /nodes/x retrieves the node" do + knife("raw -z --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning ) + expect(URI(Chef::Config.chef_server_url).port).to be > 8889 + end + end + + context "when port 9999 is already bound" do + before :each do + + @server = ChefZero::Server.new(host: "localhost", port: 9999) + @server.start_background + rescue Errno::EADDRINUSE + # OK. Don't care who has it in use, as long as *someone* does. + + end + after :each do + @server.stop if @server + end + + it "knife raw -z --chef-zero-port=9999-20000 /nodes/x" do + knife("raw -z --chef-zero-port=9999-20000 --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning ) + expect(URI(Chef::Config.chef_server_url).port).to be > 9999 + end + + it "knife raw -z --chef-zero-port=9999-9999,19423" do + knife("raw -z --chef-zero-port=9999-9999,19423 --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning ) + expect(URI(Chef::Config.chef_server_url).port).to be == 19423 + end + end + + it "knife raw -z --chef-zero-port=9999 /nodes/x retrieves the node" do + knife("raw -z --chef-zero-port=9999 --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning ) + expect(Chef::Config.chef_server_url).to eq("chefzero://localhost:9999") + end + end +end diff --git a/knife/spec/integration/config_list_spec.rb b/knife/spec/integration/config_list_spec.rb new file mode 100644 index 0000000000..5193608f36 --- /dev/null +++ b/knife/spec/integration/config_list_spec.rb @@ -0,0 +1,220 @@ +# +# Copyright 2018, Noah Kantrowitz +# +# 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 "support/shared/context/config" + +describe "knife config list", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_repository("has a custom env") do + let(:cmd_args) { [] } + let(:knife_list) do + knife("config", "list", *cmd_args, instance_filter: lambda { |instance| + # Fake the failsafe check because this command doesn't actually process knife.rb. + $__KNIFE_INTEGRATION_FAILSAFE_CHECK << " ole" + allow(File).to receive(:file?).and_call_original + }) + end + subject { knife_list.stdout } + + around do |ex| + # Store and reset the value of some env vars. + old_home = ENV["HOME"] + old_wd = Dir.pwd + # Clear these out because they are cached permanently. + ChefConfig::PathHelper.class_exec { remove_class_variable(:@@home_dir) } + Chef::Knife::ConfigList.reset_config_loader! + begin + ex.run + ensure + ENV["HOME"] = old_home + Dir.chdir(old_wd) + ENV[ChefUtils.windows? ? "CD" : "PWD"] = Dir.pwd + end + end + + before do + # Always run from the temp folder. This can't be in the `around` block above + # because it has to run after the before set in the "with a chef repo" shared context. + directory("repo") + Dir.chdir(path_to("repo")) + ENV[ChefUtils.windows? ? "CD" : "PWD"] = Dir.pwd + ENV["HOME"] = path_to(".") + allow(TTY::Screen).to receive(:width).and_return(200) + end + + # NOTE: The funky formatting with # at the end of the line of some of the + # output examples are because of how the format strings are built, there is + # substantial trailing whitespace in most cases which many editors "helpfully" remove. + + context "with no credentials file" do + subject { knife_list.stderr } + it { is_expected.to eq "FATAL: No profiles found, #{path_to(".chef/credentials")} does not exist or is empty\n" } + end + + context "with an empty credentials file" do + before { file(".chef/credentials", "") } + subject { knife_list.stderr } + it { is_expected.to eq "FATAL: No profiles found, #{path_to(".chef/credentials")} does not exist or is empty\n" } + end + + context "with a simple default profile" do + before { file(".chef/credentials", <<~EOH) } + [default] + client_name = "testuser" + client_key = "testkey.pem" + chef_server_url = "https://example.com/organizations/testorg" + EOH + it { is_expected.to eq <<~EOH.delete("#") } + Profile Client Key Server # + --------------------------------------------------------------------------------# + *default testuser ~/.chef/testkey.pem https://example.com/organizations/testorg # + EOH + end + + context "with multiple profiles" do + before { file(".chef/credentials", <<~EOH) } + [default] + client_name = "testuser" + client_key = "testkey.pem" + chef_server_url = "https://example.com/organizations/testorg" + + [prod] + client_name = "testuser" + client_key = "testkey.pem" + chef_server_url = "https://example.com/organizations/prod" + + [qa] + client_name = "qauser" + client_key = "~/src/qauser.pem" + chef_server_url = "https://example.com/organizations/testorg" + EOH + it { is_expected.to eq <<~EOH.delete("#") } + Profile Client Key Server # + --------------------------------------------------------------------------------# + *default testuser ~/.chef/testkey.pem https://example.com/organizations/testorg # + prod testuser ~/.chef/testkey.pem https://example.com/organizations/prod # + qa qauser ~/src/qauser.pem https://example.com/organizations/testorg # + EOH + end + + context "with a non-default active profile" do + let(:cmd_args) { %w{--profile prod} } + before { file(".chef/credentials", <<~EOH) } + [default] + client_name = "testuser" + client_key = "testkey.pem" + chef_server_url = "https://example.com/organizations/testorg" + + [prod] + client_name = "testuser" + client_key = "testkey.pem" + chef_server_url = "https://example.com/organizations/prod" + + [qa] + client_name = "qauser" + client_key = "~/src/qauser.pem" + chef_server_url = "https://example.com/organizations/testorg" + EOH + it { is_expected.to eq <<~EOH.delete("#") } + Profile Client Key Server # + --------------------------------------------------------------------------------# + default testuser ~/.chef/testkey.pem https://example.com/organizations/testorg # + *prod testuser ~/.chef/testkey.pem https://example.com/organizations/prod # + qa qauser ~/src/qauser.pem https://example.com/organizations/testorg # + EOH + end + + context "with a bad profile as an active profile" do + let(:cmd_args) { %w{--profile production} } + before { file(".chef/credentials", <<~EOH) } + [default] + client_name = "testuser" + client_key = "testkey.pem" + chef_server_url = "https://example.com/organizations/testorg" + + [prod] + client_name = "testuser" + client_key = "testkey.pem" + chef_server_url = "https://example.com/organizations/prod" + + [qa] + client_name = "qauser" + client_key = "~/src/qauser.pem" + chef_server_url = "https://example.com/organizations/testorg" + EOH + it { is_expected.to eq <<~EOH.delete("#") } + Profile Client Key Server # + --------------------------------------------------------------------------------# + default testuser ~/.chef/testkey.pem https://example.com/organizations/testorg # + prod testuser ~/.chef/testkey.pem https://example.com/organizations/prod # + qa qauser ~/src/qauser.pem https://example.com/organizations/testorg # + EOH + end + + context "with a minimal profile" do + before { file(".chef/credentials", <<~EOH) } + [default] + chef_server_url = "https://example.com/organizations/testorg" + EOH + it { is_expected.to match %r{^*default .*? https://example.com/organizations/testorg} } + end + + context "with -i" do + let(:cmd_args) { %w{-i} } + before { file(".chef/credentials", <<~EOH) } + [default] + chef_server_url = "https://example.com/organizations/testorg" + EOH + it { is_expected.to eq <<~EOH.delete("#") } + Profile Client Key Server # + --------------------------------------------------------------# + *default https://example.com/organizations/testorg # + EOH + end + + context "with --format=json" do + let(:cmd_args) { %w{--format=json node_name} } + before { file(".chef/credentials", <<~EOH) } + [default] + client_name = "testuser" + client_key = "testkey.pem" + chef_server_url = "https://example.com/organizations/testorg" + + [prod] + client_name = "testuser" + client_key = "testkey.pem" + chef_server_url = "https://example.com/organizations/prod" + + [qa] + client_name = "qauser" + client_key = "~/src/qauser.pem" + chef_server_url = "https://example.com/organizations/testorg" + EOH + it { + expect(JSON.parse(subject)).to eq [ + { "profile" => "default", "active" => true, "client_name" => "testuser", "client_key" => path_to(".chef/testkey.pem"), "server_url" => "https://example.com/organizations/testorg" }, + { "profile" => "prod", "active" => false, "client_name" => "testuser", "client_key" => path_to(".chef/testkey.pem"), "server_url" => "https://example.com/organizations/prod" }, + { "profile" => "qa", "active" => false, "client_name" => "qauser", "client_key" => path_to("src/qauser.pem"), "server_url" => "https://example.com/organizations/testorg" }, + ] + } + end + end +end diff --git a/knife/spec/integration/config_show_spec.rb b/knife/spec/integration/config_show_spec.rb new file mode 100644 index 0000000000..e11d001df9 --- /dev/null +++ b/knife/spec/integration/config_show_spec.rb @@ -0,0 +1,192 @@ +# +# Copyright 2018, Noah Kantrowitz +# +# 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 "support/shared/context/config" + +describe "knife config show", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + let(:cmd_args) { [] } + + when_the_repository("has a custom env") do + subject do + cmd = knife("config", "show", *cmd_args, instance_filter: lambda { |instance| + # Clear the stub set up in KnifeSupport. + allow(File).to receive(:file?).and_call_original + # Lies, damn lies, and config files. We need to allow normal config loading + # behavior to be able to test stuff. + instance.config.delete(:config_file) + $__KNIFE_INTEGRATION_FAILSAFE_CHECK << " ole" + }) + cmd.stdout + end + + around do |ex| + # Store and reset the value of some env vars. + old_chef_home = ENV["CHEF_HOME"] + old_knife_home = ENV["KNIFE_HOME"] + old_home = ENV["HOME"] + old_wd = Dir.pwd + ChefConfig::PathHelper.per_tool_home_environment = "KNIFE_HOME" + # Clear these out because they are cached permanently. + ChefConfig::PathHelper.class_exec { remove_class_variable(:@@home_dir) } + Chef::Knife::ConfigShow.reset_config_loader! + begin + ex.run + ensure + ENV["CHEF_HOME"] = old_chef_home + ENV["KNIFE_HOME"] = old_knife_home + ENV["HOME"] = old_home + Dir.chdir(old_wd) + ENV[ChefUtils.windows? ? "CD" : "PWD"] = Dir.pwd + ChefConfig::PathHelper.per_tool_home_environment = nil + end + end + + before do + # Always run from the temp folder. This can't be in the `around` block above + # because it has to run after the before set in the "with a chef repo" shared context. + directory("repo") + Dir.chdir(path_to("repo")) + ENV[ChefUtils.windows? ? "CD" : "PWD"] = Dir.pwd + ENV["HOME"] = path_to(".") + end + + context "with a global knife.rb" do + before { file(".chef/knife.rb", "node_name 'one'\n") } + + it { is_expected.to match(%r{^Loading from configuration file .*/#{File.basename(path_to("."))}/.chef/knife.rb$}) } + it { is_expected.to match(/^node_name:\s+one$/) } + end + + context "with a repo knife.rb" do + before { file("repo/.chef/knife.rb", "node_name 'two'\n") } + + it { is_expected.to match(%r{^Loading from configuration file .*/#{File.basename(path_to("."))}/repo/.chef/knife.rb$}) } + it { is_expected.to match(/^node_name:\s+two$/) } + end + + context "with both knife.rb" do + before do + file(".chef/knife.rb", "node_name 'one'\n") + file("repo/.chef/knife.rb", "node_name 'two'\n") + end + + it { is_expected.to match(%r{^Loading from configuration file .*/#{File.basename(path_to("."))}/repo/.chef/knife.rb$}) } + it { is_expected.to match(/^node_name:\s+two$/) } + end + + context "with a credentials file" do + before { file(".chef/credentials", "[default]\nclient_name = \"three\"\n") } + + it { is_expected.to match(%r{^Loading from credentials file .*/#{File.basename(path_to("."))}/.chef/credentials$}) } + it { is_expected.to match(/^node_name:\s+three$/) } + end + + context "with a credentials file and knife.rb" do + before do + file(".chef/knife.rb", "node_name 'one'\n") + file(".chef/credentials", "[default]\nclient_name = \"three\"\n") + end + + it { is_expected.to match(%r{^Loading from configuration file .*/#{File.basename(path_to("."))}/.chef/knife.rb$}) } + it { is_expected.to match(%r{^Loading from credentials file .*/#{File.basename(path_to("."))}/.chef/credentials$}) } + it { is_expected.to match(/^node_name:\s+one$/) } + end + + context "with a config dot d files" do + before { file(".chef/config.d/abc.rb", "node_name 'one'\n") } + + it { is_expected.to match(%r{^Loading from .d/ configuration file .*/#{File.basename(path_to("."))}/.chef/config.d/abc.rb$}) } + it { is_expected.to match(/^node_name:\s+one$/) } + end + + context "with a credentials file and CHEF_HOME" do + before do + file(".chef/credentials", "[default]\nclient_name = \"three\"\n") + file("foo/.chef/credentials", "[default]\nclient_name = \"four\"\n") + ENV["CHEF_HOME"] = path_to("foo") + end + + it { is_expected.to match(%r{^Loading from credentials file .*/#{File.basename(path_to("."))}/foo/.chef/credentials$}) } + it { is_expected.to match(/^node_name:\s+four$/) } + end + + context "with a credentials file and KNIFE_HOME" do + before do + file(".chef/credentials", "[default]\nclient_name = \"three\"\n") + file("bar/.chef/credentials", "[default]\nclient_name = \"four\"\n") + ENV["KNIFE_HOME"] = path_to("bar") + end + + it { is_expected.to match(%r{^Loading from credentials file .*/#{File.basename(path_to("."))}/bar/.chef/credentials$}) } + it { is_expected.to match(/^node_name:\s+four$/) } + end + + context "with single argument" do + let(:cmd_args) { %w{node_name} } + before { file(".chef/credentials", "[default]\nclient_name = \"three\"\n") } + + it { is_expected.to match(/^node_name:\s+three\Z/) } + end + + context "with two arguments" do + let(:cmd_args) { %w{node_name client_key} } + before { file(".chef/credentials", "[default]\nclient_name = \"three\"\nclient_key = \"three.pem\"") } + + it { is_expected.to match(%r{^client_key:\s+\S*/.chef/three.pem\nnode_name:\s+three\Z}) } + end + + context "with a dotted argument" do + let(:cmd_args) { %w{knife.ssh_user} } + before { file(".chef/credentials", "[default]\nclient_name = \"three\"\n[default.knife]\nssh_user = \"foo\"\n") } + + it { is_expected.to match(/^knife.ssh_user:\s+foo\Z/) } + end + + context "with regex argument" do + let(:cmd_args) { %w{/name/} } + before { file(".chef/credentials", "[default]\nclient_name = \"three\"\n") } + + it { is_expected.to match(/^node_name:\s+three\Z/) } + end + + context "with --all" do + let(:cmd_args) { %w{-a /key_contents/} } + before { file(".chef/credentials", "[default]\nclient_name = \"three\"\n") } + + it { is_expected.to match(/^client_key_contents:\s+\nvalidation_key_contents:\s+\Z/) } + end + + context "with --raw" do + let(:cmd_args) { %w{-r node_name} } + before { file(".chef/credentials", "[default]\nclient_name = \"three\"\n") } + + it { is_expected.to eq("three\n") } + end + + context "with --format=json" do + let(:cmd_args) { %w{--format=json node_name} } + before { file(".chef/credentials", "[default]\nclient_name = \"three\"\n") } + + it { expect(JSON.parse(subject)).to eq({ "node_name" => "three" }) } + end + end +end diff --git a/knife/spec/integration/config_use_spec.rb b/knife/spec/integration/config_use_spec.rb new file mode 100644 index 0000000000..4a982bc0bd --- /dev/null +++ b/knife/spec/integration/config_use_spec.rb @@ -0,0 +1,198 @@ +# +# Copyright 2018, Noah Kantrowitz +# +# 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 "support/shared/context/config" + +describe "knife config use", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + let(:cmd_args) { [] } + + when_the_repository("has a custom env") do + let(:knife_use) do + knife("config", "use", *cmd_args, instance_filter: lambda { |instance| + # Fake the failsafe check because this command doesn't actually process knife.rb. + $__KNIFE_INTEGRATION_FAILSAFE_CHECK << " ole" + allow(File).to receive(:file?).and_call_original + }) + end + + subject { knife_use.stdout } + + around do |ex| + # Store and reset the value of some env vars. + old_chef_home = ENV["CHEF_HOME"] + old_knife_home = ENV["KNIFE_HOME"] + old_home = ENV["HOME"] + old_wd = Dir.pwd + ChefConfig::PathHelper.per_tool_home_environment = "KNIFE_HOME" + # Clear these out because they are cached permanently. + ChefConfig::PathHelper.class_exec { remove_class_variable(:@@home_dir) } + Chef::Knife::ConfigUse.reset_config_loader! + begin + ex.run + ensure + ENV["CHEF_HOME"] = old_chef_home + ENV["KNIFE_HOME"] = old_knife_home + ENV["HOME"] = old_home + Dir.chdir(old_wd) + ENV[ChefUtils.windows? ? "CD" : "PWD"] = Dir.pwd + ChefConfig::PathHelper.per_tool_home_environment = nil + end + end + + before do + # Always run from the temp folder. This can't be in the `around` block above + # because it has to run after the before set in the "with a chef repo" shared context. + directory("repo") + Dir.chdir(path_to("repo")) + ENV[ChefUtils.windows? ? "CD" : "PWD"] = Dir.pwd + ENV["HOME"] = path_to(".") + end + + context "with no argument" do + context "with no configuration" do + it { is_expected.to eq "default\n" } + end + + context "with --profile" do + let(:cmd_args) { %w{--profile production} } + it { is_expected.to eq "production\n" } + end + + context "with an environment variable" do + around do |ex| + old_chef_profile = ENV["CHEF_PROFILE"] + begin + ENV["CHEF_PROFILE"] = "staging" + ex.run + ensure + ENV["CHEF_PROFILE"] = old_chef_profile + end + end + + it { is_expected.to eq "staging\n" } + end + + context "with a context file" do + before { file(".chef/context", "development\n") } + it { is_expected.to eq "development\n" } + end + + context "with a context file under $CHEF_HOME" do + before do + file("chefhome/.chef/context", "other\n") + ENV["CHEF_HOME"] = path_to("chefhome") + end + + it { is_expected.to eq "other\n" } + end + + context "with a context file under $KNIFE_HOME" do + before do + file("knifehome/.chef/context", "other\n") + ENV["KNIFE_HOME"] = path_to("knifehome") + end + + it { is_expected.to eq "other\n" } + end + end + + context "with an argument" do + let(:cmd_args) { %w{production} } + before { file(".chef/credentials", <<~EOH) } + [production] + client_name = "testuser" + client_key = "testkey.pem" + chef_server_url = "https://example.com/organizations/testorg" + EOH + it do + is_expected.to eq "Set default profile to production\n" + expect(File.read(path_to(".chef/context"))).to eq "production\n" + end + end + + context "with no credentials file" do + let(:cmd_args) { %w{production} } + subject { knife_use.stderr } + it { is_expected.to eq "FATAL: No profiles found, #{path_to(".chef/credentials")} does not exist or is empty\n" } + end + + context "with an empty credentials file" do + let(:cmd_args) { %w{production} } + before { file(".chef/credentials", "") } + subject { knife_use.stderr } + it { is_expected.to eq "FATAL: No profiles found, #{path_to(".chef/credentials")} does not exist or is empty\n" } + end + + context "with an wrong argument" do + let(:cmd_args) { %w{staging} } + before { file(".chef/credentials", <<~EOH) } + [production] + client_name = "testuser" + client_key = "testkey.pem" + chef_server_url = "https://example.com/organizations/testorg" + EOH + subject { knife_use } + it { expect { subject }.to raise_error ChefConfig::ConfigurationError, "Profile staging doesn't exist. Please add it to #{path_to(".chef/credentials")} and if it is profile with DNS name check that you are not missing single quotes around it as per docs https://docs.chef.io/workstation/knife_setup/#knife-profiles." } + end + + context "with $CHEF_HOME" do + let(:cmd_args) { %w{staging} } + before do + ENV["CHEF_HOME"] = path_to("chefhome"); file("chefhome/tmp", "") + file("chefhome/.chef/credentials", <<~EOH + [staging] + client_name = "testuser" + client_key = "testkey.pem" + chef_server_url = "https://example.com/organizations/testorg" + EOH + ) + end + + it do + is_expected.to eq "Set default profile to staging\n" + expect(File.read(path_to("chefhome/.chef/context"))).to eq "staging\n" + expect(File.exist?(path_to(".chef/context"))).to be_falsey + end + end + + context "with $KNIFE_HOME" do + let(:cmd_args) { %w{development} } + + before do + ENV["KNIFE_HOME"] = path_to("knifehome"); file("knifehome/tmp", "") + file("knifehome/.chef/credentials", <<~EOH + [development] + client_name = "testuser" + client_key = "testkey.pem" + chef_server_url = "https://example.com/organizations/testorg" + EOH + ) + end + + it do + is_expected.to eq "Set default profile to development\n" + expect(File.read(path_to("knifehome/.chef/context"))).to eq "development\n" + expect(File.exist?(path_to(".chef/context"))).to be_falsey + end + end + end +end diff --git a/knife/spec/integration/cookbook_api_ipv6_spec.rb b/knife/spec/integration/cookbook_api_ipv6_spec.rb new file mode 100644 index 0000000000..5d0ce0707f --- /dev/null +++ b/knife/spec/integration/cookbook_api_ipv6_spec.rb @@ -0,0 +1,113 @@ +# +# Author:: Daniel DeLeo (<dan@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/mixin/shell_out" + +describe "Knife cookbook API integration with IPv6", :workstation, :not_supported_on_gce do + include IntegrationSupport + include Chef::Mixin::ShellOut + + when_the_chef_server "is bound to IPv6" do + let(:chef_zero_opts) { { host: "::1" } } + + let(:client_key) do + <<~END_VALIDATION_PEM + -----BEGIN RSA PRIVATE KEY----- + MIIEogIBAAKCAQEApubutqtYYQ5UiA9QhWP7UvSmsfHsAoPKEVVPdVW/e8Svwpyf + 0Xef6OFWVmBE+W442ZjLOe2y6p2nSnaq4y7dg99NFz6X+16mcKiCbj0RCiGqCvCk + NftHhTgO9/RFvCbmKZ1RKNob1YzLrFpxBHaSh9po+DGWhApcd+I+op+ZzvDgXhNn + 0nauZu3rZmApI/r7EEAOjFedAXs7VPNXhhtZAiLSAVIrwU3ZajtSzgXOxbNzgj5O + AAAMmThK+71qPdffAdO4J198H6/MY04qgtFo7vumzCq0UCaGZfmeI1UNE4+xQWwP + HJ3pDAP61C6Ebx2snI2kAd9QMx9Y78nIedRHPwIDAQABAoIBAHssRtPM1GacWsom + 8zfeN6ZbI4KDlbetZz0vhnqDk9NVrpijWlcOP5dwZXVNitnB/HaqCqFvyPDY9JNB + zI/pEFW4QH59FVDP42mVEt0keCTP/1wfiDDGh1vLqVBYl/ZphscDcNgDTzNkuxMx + k+LFVxKnn3w7rGc59lALSkpeGvbbIDjp3LUMlUeCF8CIFyYZh9ZvXe4OCxYdyjxb + i8tnMLKvJ4Psbh5jMapsu3rHQkfPdqzztQUz8vs0NYwP5vWge46FUyk+WNm/IhbJ + G3YM22nwUS8Eu2bmTtADSJolATbCSkOwQ1D+Fybz/4obfYeGaCdOqB05ttubhenV + ShsAb7ECgYEA20ecRVxw2S7qA7sqJ4NuYOg9TpfGooptYNA1IP971eB6SaGAelEL + awYkGNuu2URmm5ElZpwJFFTDLGA7t2zB2xI1FeySPPIVPvJGSiZoFQOVlIg9WQzK + 7jTtFQ/tOMrF+bigEUJh5bP1/7HzqSpuOsPjEUb2aoCTp+tpiRGL7TUCgYEAwtns + g3ysrSEcTzpSv7fQRJRk1lkBhatgNd0oc+ikzf74DaVLhBg1jvSThDhiDCdB59mr + Jh41cnR1XqE8jmdQbCDRiFrI1Pq6TPaDZFcovDVE1gue9x86v3FOH2ukPG4d2/Xy + HevXjThtpMMsWFi0JYXuzXuV5HOvLZiP8sN3lSMCgYANpdxdGM7RRbE9ADY0dWK2 + V14ReTLcxP7fyrWz0xLzEeCqmomzkz3BsIUoouu0DCTSw+rvAwExqcDoDylIVlWO + fAifz7SeZHbcDxo+3TsXK7zwnLYsx7YNs2+aIv6hzUUbMNmNmXMcZ+IEwx+mRMTN + lYmZdrA5mr0V83oDFPt/jQKBgC74RVE03pMlZiObFZNtheDiPKSG9Bz6wMh7NWMr + c37MtZLkg52mEFMTlfPLe6ceV37CM8WOhqe+dwSGrYhOU06dYqUR7VOZ1Qr0aZvo + fsNPu/Y0+u7rMkgv0fs1AXQnvz7kvKaF0YITVirfeXMafuKEtJoH7owRbur42cpV + YCAtAoGAP1rHOc+w0RUcBK3sY7aErrih0OPh9U5bvJsrw1C0FIZhCEoDVA+fNIQL + syHLXYFNy0OxMtH/bBAXBGNHd9gf5uOnqh0pYcbe/uRAxumC7Rl0cL509eURiA2T + +vFmf54y9YdnLXaqv+FhJT6B6V7WX7IpU9BMqJY1cJYXHuHG2KA= + -----END RSA PRIVATE KEY----- + END_VALIDATION_PEM + end + + let(:cache_path) do + Dir.mktmpdir + end + + let(:chef_dir) { File.join(__dir__, "..", "..", "..", "knife", "bin") } + let(:knife) { "ruby '#{chef_dir}/knife'" } + + let(:knife_config_flag) { "-c '#{path_to("config/knife.rb")}'" } + + # Some Solaris test platforms are too old for IPv6. These tests should not + # otherwise be platform dependent, so exclude solaris + context "and the chef_server_url contains an IPv6 literal", :not_supported_on_solaris do + + # This provides helper functions we need such as #path_to() + when_the_repository "has the cookbook to be uploaded" do + + let(:knife_rb_content) do + <<~END_CLIENT_RB + chef_server_url "http://[::1]:8900" + syntax_check_cache_path '#{cache_path}' + client_key '#{path_to("config/knifeuser.pem")}' + node_name 'whoisthisis' + cookbook_path '#{CHEF_SPEC_DATA}/cookbooks' + END_CLIENT_RB + end + + before do + file "config/knife.rb", knife_rb_content + file "config/knifeuser.pem", client_key + end + + it "successfully uploads a cookbook" do + shell_out!("#{knife} cookbook upload apache2 #{knife_config_flag}", cwd: chef_dir) + versions_list_json = Chef::HTTP::Simple.new("http://[::1]:8900").get("/cookbooks/apache2", "accept" => "application/json") + versions_list = Chef::JSONCompat.from_json(versions_list_json) + expect(versions_list["apache2"]["versions"]).not_to be_empty + end + + context "and the cookbook has been uploaded to the server" do + before do + shell_out!("#{knife} cookbook upload apache2 #{knife_config_flag}", cwd: chef_dir) + end + + it "downloads the cookbook" do + shell_out!("#{knife} cookbook download apache2 #{knife_config_flag} -d #{cache_path}", cwd: chef_dir) + expect(Dir["#{cache_path}/*"].map { |entry| File.basename(entry) }).to include("apache2-0.0.1") + end + end + + end + end + end +end diff --git a/knife/spec/integration/cookbook_bulk_delete_spec.rb b/knife/spec/integration/cookbook_bulk_delete_spec.rb new file mode 100644 index 0000000000..0e791f5a1e --- /dev/null +++ b/knife/spec/integration/cookbook_bulk_delete_spec.rb @@ -0,0 +1,65 @@ +# +# 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 "support/shared/context/config" +require "chef/knife/cookbook_bulk_delete" + +describe "knife cookbook bulk delete", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has a cookbook" do + before do + cookbook "foo", "1.0.0" + cookbook "foo", "0.6.5" + cookbook "fox", "0.6.0" + cookbook "fox", "0.6.5" + cookbook "fax", "0.6.0" + cookbook "zfa", "0.6.5" + end + + # rubocop:disable Layout/TrailingWhitespace + it "knife cookbook bulk delete deletes all matching cookbooks" do + stdout = <<~EOM + All versions of the following cookbooks will be deleted: + + foo fox + + Do you really want to delete these cookbooks? (Y/N) + EOM + + stderr = <<~EOM + Deleted cookbook foo [1.0.0] + Deleted cookbook foo [0.6.5] + Deleted cookbook fox [0.6.5] + Deleted cookbook fox [0.6.0] + EOM + + knife("cookbook bulk delete ^fo.*", input: "Y").should_succeed(stderr: stderr, stdout: stdout) + + knife("cookbook list -a").should_succeed <<~EOM + fax 0.6.0 + zfa 0.6.5 + EOM + end + # rubocop:enable Layout/TrailingWhitespace + + end +end diff --git a/knife/spec/integration/cookbook_download_spec.rb b/knife/spec/integration/cookbook_download_spec.rb new file mode 100644 index 0000000000..589417126c --- /dev/null +++ b/knife/spec/integration/cookbook_download_spec.rb @@ -0,0 +1,72 @@ +# +# 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 "support/shared/context/config" +require "chef/knife/cookbook_download" +require "tmpdir" + +describe "knife cookbook download", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + let(:tmpdir) { Dir.mktmpdir } + + when_the_chef_server "has only one cookbook" do + before do + cookbook "x", "1.0.1" + end + + it "knife cookbook download downloads the latest version" do + knife("cookbook download -d #{tmpdir} x").should_succeed stderr: <<~EOM + Downloading x cookbook version 1.0.1 + Downloading root_files + Cookbook downloaded to #{tmpdir}/x-1.0.1 + EOM + end + + it "knife cookbook download with a version downloads the specified version" do + knife("cookbook download -d #{tmpdir} x 1.0.1").should_succeed stderr: <<~EOM + Downloading x cookbook version 1.0.1 + Downloading root_files + Cookbook downloaded to #{tmpdir}/x-1.0.1 + EOM + end + + it "knife cookbook download with an unknown version raises an error" do + expect { knife("cookbook download -d #{tmpdir} x 1.0.0") }.to raise_error(Net::HTTPClientException) + end + end + + when_the_chef_server "has multiple cookbook versions" do + before do + cookbook "x", "1.0.1" + cookbook "x", "1.0.0" + end + + it "knife cookbook download with no version prompts" do + knife("cookbook download -d #{tmpdir} x", input: "2\n").should_succeed(stderr: <<~EOM, stdout: "Which version do you want to download?\n1. x 1.0.0\n2. x 1.0.1\n\n" + Downloading x cookbook version 1.0.1 + Downloading root_files + Cookbook downloaded to #{tmpdir}/x-1.0.1 + EOM + ) + end + end +end diff --git a/knife/spec/integration/cookbook_list_spec.rb b/knife/spec/integration/cookbook_list_spec.rb new file mode 100644 index 0000000000..e712ae3235 --- /dev/null +++ b/knife/spec/integration/cookbook_list_spec.rb @@ -0,0 +1,55 @@ +# +# 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 "support/shared/context/config" +require "chef/knife/cookbook_list" + +describe "knife cookbook list", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has a cookbook" do + before do + cookbook "x", "1.0.0" + cookbook "x", "0.6.5" + cookbook "x", "0.6.0" + cookbook "y", "0.6.5" + cookbook "y", "0.6.0" + cookbook "z", "0.6.5" + end + + it "knife cookbook list shows all the cookbooks" do + knife("cookbook list").should_succeed <<~EOM + x 1.0.0 + y 0.6.5 + z 0.6.5 + EOM + end + + it "knife cookbook list -a shows all the versions of all the cookbooks" do + knife("cookbook list -a").should_succeed <<~EOM + x 1.0.0 0.6.5 0.6.0 + y 0.6.5 0.6.0 + z 0.6.5 + EOM + end + + end +end diff --git a/knife/spec/integration/cookbook_show_spec.rb b/knife/spec/integration/cookbook_show_spec.rb new file mode 100644 index 0000000000..d8c2e38f64 --- /dev/null +++ b/knife/spec/integration/cookbook_show_spec.rb @@ -0,0 +1,149 @@ +# +# 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 "support/shared/context/config" +require "chef/knife/cookbook_show" + +describe "knife cookbook show", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has a cookbook" do + before do + cookbook "x", "1.0.0", { "recipes" => { "default.rb" => "file 'n'", "x.rb" => "" } } + cookbook "x", "0.6.5" + end + + it "knife cookbook show x shows all the versions" do + knife("cookbook show x").should_succeed "x 1.0.0 0.6.5\n" + end + + # rubocop:disable Layout/TrailingWhitespace + it "knife cookbook show x 1.0.0 shows the correct version" do + knife("cookbook show x 1.0.0").should_succeed <<~EOM + cookbook_name: x + frozen?: false + metadata: + chef_versions: + dependencies: + description: + eager_load_libraries: true + gems: + issues_url: + license: All rights reserved + long_description: + maintainer: + maintainer_email: + name: x + ohai_versions: + platforms: + privacy: false + providing: + x: >= 0.0.0 + x::x: >= 0.0.0 + recipes: + x: + x::x: + source_url: + version: 1.0.0 + name: x-1.0.0 + recipes: + checksum: 4631b34cf58de10c5ef1304889941b2e + name: recipes/default.rb + path: recipes/default.rb + specificity: default + url: http://127.0.0.1:8900/file_store/checksums/4631b34cf58de10c5ef1304889941b2e + + checksum: d41d8cd98f00b204e9800998ecf8427e + name: recipes/x.rb + path: recipes/x.rb + specificity: default + url: http://127.0.0.1:8900/file_store/checksums/d41d8cd98f00b204e9800998ecf8427e + root_files: + checksum: 8226671f751ba102dea6a6b6bd32fa8d + name: metadata.rb + path: metadata.rb + specificity: default + url: http://127.0.0.1:8900/file_store/checksums/8226671f751ba102dea6a6b6bd32fa8d + version: 1.0.0 + EOM + end + + it "knife cookbook show x 1.0.0 metadata shows the metadata" do + knife("cookbook show x 1.0.0 metadata").should_succeed <<~EOM + chef_versions: + dependencies: + description: + eager_load_libraries: true + gems: + issues_url: + license: All rights reserved + long_description: + maintainer: + maintainer_email: + name: x + ohai_versions: + platforms: + privacy: false + providing: + x: >= 0.0.0 + x::x: >= 0.0.0 + recipes: + x: + x::x: + source_url: + version: 1.0.0 + EOM + end + + it "knife cookbook show x 1.0.0 recipes shows all the recipes" do + knife("cookbook show x 1.0.0 recipes").should_succeed <<~EOM + checksum: 4631b34cf58de10c5ef1304889941b2e + name: recipes/default.rb + path: recipes/default.rb + specificity: default + url: http://127.0.0.1:8900/file_store/checksums/4631b34cf58de10c5ef1304889941b2e + + checksum: d41d8cd98f00b204e9800998ecf8427e + name: recipes/x.rb + path: recipes/x.rb + specificity: default + url: http://127.0.0.1:8900/file_store/checksums/d41d8cd98f00b204e9800998ecf8427e + EOM + end + # rubocop:enable Layout/TrailingWhitespace + + it "knife cookbook show x 1.0.0 recipes default.rb shows the default recipe" do + knife("cookbook show x 1.0.0 recipes default.rb").should_succeed "file 'n'\n" + end + + it "knife cookbook show with a non-existent file displays an error" do + expect { knife("cookbook show x 1.0.0 recipes moose.rb") }.to raise_error(Chef::Exceptions::FileNotFound) + end + + it "knife cookbook show with a non-existent version displays an error" do + expect { knife("cookbook show x 1.0.1") }.to raise_error(Net::HTTPClientException) + end + + it "knife cookbook show with a non-existent cookbook displays an error" do + expect { knife("cookbook show y") }.to raise_error(Net::HTTPClientException) + end + end +end diff --git a/knife/spec/integration/cookbook_upload_spec.rb b/knife/spec/integration/cookbook_upload_spec.rb new file mode 100644 index 0000000000..f42683b2a3 --- /dev/null +++ b/knife/spec/integration/cookbook_upload_spec.rb @@ -0,0 +1,128 @@ +# +# 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 "support/shared/context/config" +require "chef/knife/cookbook_upload" + +describe "knife cookbook upload", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + let(:cb_dir) { "#{@repository_dir}/cookbooks" } + + when_the_chef_server "is empty" do + when_the_repository "has a cookbook" do + before do + file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0") + end + + it "knife cookbook upload uploads the cookbook" do + knife("cookbook upload x -o #{cb_dir}").should_succeed stderr: <<~EOM + Uploading x [1.0.0] + Uploaded 1 cookbook. + EOM + end + + it "knife cookbook upload --freeze uploads and freezes the cookbook" do + knife("cookbook upload x -o #{cb_dir} --freeze").should_succeed stderr: <<~EOM + Uploading x [1.0.0] + Uploaded 1 cookbook. + EOM + # Modify the file, attempt to reupload + file "cookbooks/x/metadata.rb", 'name "x"; version "1.0.0"#different' + knife("cookbook upload x -o #{cb_dir} --freeze").should_fail stderr: <<~EOM + Uploading x [1.0.0] + ERROR: Version 1.0.0 of cookbook x is frozen. Use --force to override. + WARNING: Not updating version constraints for x in the environment as the cookbook is frozen. + ERROR: Failed to upload 1 cookbook. + EOM + end + end + + when_the_repository "has a cookbook that depends on another cookbook" do + before do + file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0", "\ndepends 'y'") + file "cookbooks/y/metadata.rb", cb_metadata("y", "1.0.0") + end + + it "knife cookbook upload --include-dependencies uploads both cookbooks" do + knife("cookbook upload --include-dependencies x -o #{cb_dir}").should_succeed stderr: <<~EOM + Uploading x [1.0.0] + Uploading y [1.0.0] + Uploaded 2 cookbooks. + EOM + end + + it "knife cookbook upload fails due to missing dependencies" do + knife("cookbook upload x -o #{cb_dir}").should_fail stderr: <<~EOM + Uploading x [1.0.0] + ERROR: Cookbook x depends on cookbooks which are not currently + ERROR: being uploaded and cannot be found on the server. + ERROR: The missing cookbook(s) are: 'y' version '>= 0.0.0' + EOM + end + + it "knife cookbook upload -a uploads both cookbooks" do + knife("cookbook upload -a -o #{cb_dir}").should_succeed stderr: <<~EOM + Uploading x [1.0.0] + Uploading y [1.0.0] + Uploaded all cookbooks. + EOM + end + end + + when_the_repository "has cookbook metadata without name attribute in metadata file" do + before do + file "cookbooks/x/metadata.rb", cb_metadata(nil, "1.0.0") + end + + it "knife cookbook upload x " do + expect { knife("cookbook upload x -o #{cb_dir}") }.to raise_error(Chef::Exceptions::MetadataNotValid) + end + end + + when_the_repository "has cookbooks at multiple paths" do + + let(:cb_dir_first) do + File.join(@repository_dir, "cookbooks") + .gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR) + end + + let(:cb_dir_second) do + File.join(@repository_dir, "test_cookbooks") + .gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR) + end + + before(:each) do + file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0") + file "test_cookbooks/y/metadata.rb", cb_metadata("y", "1.0.0") + end + + it "knife cookbook upload with -o or --cookbook-path" do + knife("cookbook upload x y -o #{cb_dir_first}#{File::PATH_SEPARATOR}#{cb_dir_second}").should_succeed stderr: <<~EOM + Uploading x [1.0.0] + Uploading y [1.0.0] + Uploaded 2 cookbooks. + EOM + end + + end + end +end diff --git a/knife/spec/integration/data_bag_create_spec.rb b/knife/spec/integration/data_bag_create_spec.rb new file mode 100644 index 0000000000..439d69507c --- /dev/null +++ b/knife/spec/integration/data_bag_create_spec.rb @@ -0,0 +1,125 @@ +# +# 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 "support/shared/context/config" +require "chef/knife/data_bag_create" + +describe "knife data bag create", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + let(:err) { "Created data_bag[foo]\n" } + let(:out) { "Created data_bag_item[bar]\n" } + let(:exists) { "Data bag foo already exists\n" } + let(:secret) { "abc" } + + when_the_chef_server "is empty" do + context "with encryption key" do + it "creates a new data bag and item" do + pretty_json = Chef::JSONCompat.to_json_pretty({ id: "bar", test: "pass" }) + allow(Chef::JSONCompat).to receive(:to_json_pretty).and_return(pretty_json) + knife("data bag create foo bar --secret #{secret}").should_succeed stdout: out, stderr: err + expect(knife("data bag show foo bar --secret #{secret}").stderr).to eq("Encrypted data bag detected, decrypting with provided secret.\n") + expect(knife("data bag show foo bar --secret #{secret}").stdout).to eq("id: bar\ntest: pass\n") + end + + it "creates a new data bag and an empty item" do + knife("data bag create foo bar --secret #{secret}").should_succeed stdout: out, stderr: err + expect(knife("data bag show foo bar --secret #{secret}").stderr).to eq("WARNING: Unencrypted data bag detected, ignoring any provided secret options.\n") + expect(knife("data bag show foo bar --secret #{secret}").stdout).to eq("id: bar\n") + end + end + + context "without encryption key" do + it "creates a new data bag" do + knife("data bag create foo").should_succeed stderr: err + expect(knife("data bag show foo").stderr).to eq("") + end + + it "creates a new data bag and item" do + knife("data bag create foo bar").should_succeed stdout: out, stderr: err + expect(knife("data bag show foo").stdout).to eq("bar\n") + end + end + end + + when_the_chef_server "has some data bags" do + before do + data_bag "foo", {} + data_bag "bag", { "box" => {} } + end + + context "with encryption key" do + it "creates a new data bag and item" do + pretty_json = Chef::JSONCompat.to_json_pretty({ id: "bar", test: "pass" }) + allow(Chef::JSONCompat).to receive(:to_json_pretty).and_return(pretty_json) + knife("data bag create rocket bar --secret #{secret}").should_succeed stdout: out, stderr: <<~EOM + Created data_bag[rocket] + EOM + expect(knife("data bag show rocket bar --secret #{secret}").stderr).to eq("Encrypted data bag detected, decrypting with provided secret.\n") + expect(knife("data bag show rocket bar --secret #{secret}").stdout).to eq("id: bar\ntest: pass\n") + end + + it "creates a new data bag and an empty item" do + knife("data bag create rocket bar --secret #{secret}").should_succeed stdout: out, stderr: <<~EOM + Created data_bag[rocket] + EOM + expect(knife("data bag show rocket bar --secret #{secret}").stderr).to eq("WARNING: Unencrypted data bag detected, ignoring any provided secret options.\n") + expect(knife("data bag show rocket bar --secret #{secret}").stdout).to eq("id: bar\n") + end + + it "adds a new item to an existing bag" do + knife("data bag create foo bar --secret #{secret}").should_succeed stdout: out, stderr: exists + expect(knife("data bag show foo bar --secret #{secret}").stderr).to eq("WARNING: Unencrypted data bag detected, ignoring any provided secret options.\n") + expect(knife("data bag show foo bar --secret #{secret}").stdout).to eq("id: bar\n") + end + + it "fails to add an existing item" do + expect { knife("data bag create bag box --secret #{secret}") }.to raise_error(Net::HTTPClientException) + end + end + + context "without encryption key" do + it "creates a new data bag" do + knife("data bag create rocket").should_succeed stderr: <<~EOM + Created data_bag[rocket] + EOM + end + + it "creates a new data bag and item" do + knife("data bag create rocket bar").should_succeed stdout: out, stderr: <<~EOM + Created data_bag[rocket] + EOM + end + + it "adds a new item to an existing bag" do + knife("data bag create foo bar").should_succeed stdout: out, stderr: exists + end + + it "refuses to create an existing data bag" do + knife("data bag create foo").should_succeed stderr: exists + end + + it "fails to add an existing item" do + expect { knife("data bag create bag box") }.to raise_error(Net::HTTPClientException) + end + end + end +end diff --git a/knife/spec/integration/data_bag_delete_spec.rb b/knife/spec/integration/data_bag_delete_spec.rb new file mode 100644 index 0000000000..a7fac7e2ee --- /dev/null +++ b/knife/spec/integration/data_bag_delete_spec.rb @@ -0,0 +1,59 @@ +# +# 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 "support/shared/context/config" +require "chef/knife/data_bag_delete" + +describe "knife data bag delete", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has some data bags" do + before do + data_bag "x", {} + data_bag "canteloupe", {} + data_bag "rocket", { "falcon9" => { heavy: "true" }, "atlas" => {}, "ariane" => {} } + end + + it "with an empty data bag" do + knife("data bag delete canteloupe", input: "y").should_succeed <<~EOM + Do you really want to delete canteloupe? (Y/N) Deleted data_bag[canteloupe] + EOM + end + + it "with a bag with some items" do + knife("data bag delete rocket", input: "y").should_succeed <<~EOM + Do you really want to delete rocket? (Y/N) Deleted data_bag[rocket] + EOM + end + + it "with a single item" do + knife("data bag delete rocket falcon9", input: "y").should_succeed <<~EOM + Do you really want to delete falcon9? (Y/N) Deleted data_bag_item[falcon9] + EOM + end + + it "choosing not to delete" do + knife("data bag delete rocket falcon9", input: "n").should_succeed <<~EOM, exit_code: 3 + Do you really want to delete falcon9? (Y/N) You said no, so I'm done here. + EOM + end + end +end diff --git a/knife/spec/integration/data_bag_edit_spec.rb b/knife/spec/integration/data_bag_edit_spec.rb new file mode 100644 index 0000000000..1071df2a78 --- /dev/null +++ b/knife/spec/integration/data_bag_edit_spec.rb @@ -0,0 +1,105 @@ +# +# 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 "support/shared/context/config" +require "chef/knife/data_bag_edit" + +describe "knife data bag edit", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + let(:out) { "Saved data_bag_item[box]\n" } + let(:err) { "Saving data bag unencrypted. To encrypt it, provide an appropriate secret.\n" } + let(:secret) { "abc" } + let(:encrypt) { "Encrypted data bag detected, decrypting with provided secret.\n" } + + when_the_chef_server "is empty" do + context "with encryption key" do + it "fails to edit an item" do + expect { knife("data bag edit bag box --secret #{secret}") }.to raise_error(Net::HTTPClientException) + end + end + + context "without encryption key" do + it "fails to edit an item" do + expect { knife("data bag edit bag box") }.to raise_error(Net::HTTPClientException) + end + end + end + + when_the_chef_server "has some data bags" do + before do + data_bag "foo", {} + data_bag "bag", { "box" => {} } + data_bag "rocket", { "falcon9" => { heavy: "true" }, "atlas" => {}, "ariane" => {} } + data_bag "encrypt", { "box" => { id: "box", foo: { "encrypted_data": "J8N0pJ+LFDQF3XvhzWgkSBOuZZn8Og==\n", "iv": "4S1sb4zLnMt71SXV\n", "auth_tag": "4ChINhxz4WmqOizvZNoPPg==\n", "version": 3, "cipher": "aes-256-gcm" } } } + end + + context "with encryption key" do + it "fails to edit a non-existing item" do + expect { knife("data bag edit foo box --secret #{secret}") }.to raise_error(Net::HTTPClientException) + end + + it "edits an encrypted data bag item" do + pretty_json = Chef::JSONCompat.to_json_pretty({ id: "box", foo: "bar" }) + allow(Chef::JSONCompat).to receive(:to_json_pretty).and_return(pretty_json) + knife("data bag edit encrypt box --secret #{secret}") + knife("data bag show encrypt box --secret #{secret}").should_succeed stderr: encrypt, stdout: <<~EOM + foo: bar + id: box + EOM + end + + it "encrypts an unencrypted data bag item" do + knife("data bag edit rocket falcon9 --secret #{secret}") + knife("data bag show rocket falcon9 --secret #{secret}").should_succeed stderr: encrypt, stdout: <<~EOM + heavy: true + id: falcon9 + EOM + end + end + + context "without encryption key" do + it "fails to edit a non-existing item" do + expect { knife("data bag edit foo box") }.to raise_error(Net::HTTPClientException) + end + it "edits an empty data bag item" do + pretty_json = Chef::JSONCompat.to_json_pretty({ id: "box", ab: "abc" }) + allow(Chef::JSONCompat).to receive(:to_json_pretty).and_return(pretty_json) + knife("data bag edit bag box").should_succeed stderr: err, stdout: out + knife("data bag show bag box").should_succeed <<~EOM + ab: abc + id: box + EOM + end + it "edits a non-empty data bag item" do + pretty_json = Chef::JSONCompat.to_json_pretty({ id: "falcon9", heavy: false }) + allow(Chef::JSONCompat).to receive(:to_json_pretty).and_return(pretty_json) + knife("data bag edit rocket falcon9").should_succeed stderr: err, stdout: <<~EOM + Saved data_bag_item[falcon9] + EOM + knife("data bag show rocket falcon9").should_succeed <<~EOM + heavy: false + id: falcon9 + EOM + end + end + end +end diff --git a/knife/spec/integration/data_bag_from_file_spec.rb b/knife/spec/integration/data_bag_from_file_spec.rb new file mode 100644 index 0000000000..bb8bd192f0 --- /dev/null +++ b/knife/spec/integration/data_bag_from_file_spec.rb @@ -0,0 +1,116 @@ +# +# 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 "support/shared/context/config" + +describe "knife data bag from file", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + let(:db_dir) { "#{@repository_dir}/data_bags" } + + when_the_chef_server "has an empty data bag" do + before do + data_bag "foo", {} + data_bag "bar", {} + end + + when_the_repository "has some data bag items" do + before do + file "data_bags/foo/bar.json", { "id" => "bar", "foo" => "bar " } + file "data_bags/foo/bzr.json", { "id" => "bzr", "foo" => "bar " } + file "data_bags/foo/cat.json", { "id" => "cat", "foo" => "bar " } + file "data_bags/foo/dog.json", { "id" => "dog", "foo" => "bar " } + file "data_bags/foo/encrypted.json", <<~EOM + { + "id": "encrypted", + "password": { + "encrypted_data": "H6ab5RY9a9JAkS8A0RCMspXtOJh0ai8cNeA4Q3gLO8s=\\n", + "iv": "uWKKKxrJgtELlGMCOLJdkA==\\n", + "version": 1, + "cipher": "aes-256-cbc" + } + } + EOM + file "data_bags/bar/round_trip.json", <<~EOM + { + "name": "data_bag_item_bar_round_trip", + "json_class": "Chef::DataBagItem", + "chef_type": "data_bag_item", + "data_bag": "bar", + "raw_data": { + "id": "round_trip", + "root_password": { + "encrypted_data": "noDOsTpsTAZlTU5sprhmYZzUDfr8du7hH/zRDOjRAmoTJHTZyfYoR221EOOW\\nXJ1D\\n", + "iv": "Bnqhfy6n0Hx1wCe9pxHLoA==\\n", + "version": 1, + "cipher": "aes-256-cbc" + }, + "admin_password": { + "encrypted_data": "TcC7dU1gx6OnE5Ab4i/k42UEf0Nnr7cAyuTHId/LNjNOwpNf7XZc27DQSjuy\\nHPlt\\n", + "iv": "+TAWJuPWCI2+WB8lGJAyvw==\\n", + "version": 1, + "cipher": "aes-256-cbc" + } + } + } + EOM + end + + it "uploads a single file" do + knife("data bag from file foo #{db_dir}/foo/bar.json").should_succeed stderr: <<~EOM + Updated data_bag_item[foo::bar] + EOM + end + + it "uploads a single encrypted file" do + knife("data bag from file foo #{db_dir}/foo/encrypted.json").should_succeed stderr: <<~EOM + Updated data_bag_item[foo::encrypted] + EOM + end + + it "uploads a file in chef's internal format" do + pending "chef/chef#4815" + knife("data bag from file bar #{db_dir}/bar/round_trip.json").should_succeed stderr: <<~EOM + Updated data_bag_item[bar::round_trip] + EOM + end + + it "uploads many files" do + knife("data bag from file foo #{db_dir}/foo/bar.json #{db_dir}/foo/bzr.json").should_succeed stderr: <<~EOM + Updated data_bag_item[foo::bar] + Updated data_bag_item[foo::bzr] + EOM + end + + it "uploads a whole directory" do + knife("data bag from file foo #{db_dir}/foo") + knife("data bag show foo").should_succeed <<~EOM + bar + bzr + cat + dog + encrypted + EOM + end + + end + end +end diff --git a/knife/spec/integration/data_bag_list_spec.rb b/knife/spec/integration/data_bag_list_spec.rb new file mode 100644 index 0000000000..1e7734db64 --- /dev/null +++ b/knife/spec/integration/data_bag_list_spec.rb @@ -0,0 +1,44 @@ +# +# 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 "support/shared/context/config" +require "chef/knife/data_bag_list" + +describe "knife data bag list", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has some data bags" do + before do + data_bag "x", {} + data_bag "canteloupe", {} + data_bag "rocket", {} + end + + it "knife data bag list shows all the cookbooks" do + knife("data bag list").should_succeed <<~EOM + canteloupe + rocket + x + EOM + end + + end +end diff --git a/knife/spec/integration/data_bag_show_spec.rb b/knife/spec/integration/data_bag_show_spec.rb new file mode 100644 index 0000000000..91ebf605f1 --- /dev/null +++ b/knife/spec/integration/data_bag_show_spec.rb @@ -0,0 +1,95 @@ +# +# 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 "support/shared/context/config" +require "chef/knife/data_bag_show" + +describe "knife data bag show", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "is empty" do + it "raises error if try to retrieve it" do + expect { knife("data bag show bag") }.to raise_error(Net::HTTPClientException) + end + end + + when_the_chef_server "contains data bags" do + let(:right_secret) { "abc" } + let(:wrong_secret) { "ab" } + let(:err) { "Encrypted data bag detected, decrypting with provided secret.\n" } + before do + data_bag "x", {} + data_bag "canteloupe", {} + data_bag "rocket", { "falcon9" => { heavy: "true" }, "atlas" => {}, "ariane" => {} } + data_bag "encrypt", { "box" => { id: "box", foo: { "encrypted_data": "J8N0pJ+LFDQF3XvhzWgkSBOuZZn8Og==\n", "iv": "4S1sb4zLnMt71SXV\n", "auth_tag": "4ChINhxz4WmqOizvZNoPPg==\n", "version": 3, "cipher": "aes-256-gcm" } } } + end + + context "with encrypted data" do + context "provided secret key" do + it "shows data if secret key is correct" do + knife("data bag show encrypt box --secret #{right_secret}").should_succeed stderr: err, stdout: <<~EOM + foo: bar + id: box + EOM + end + + it "raises error if secret key is incorrect" do + expect { knife("data bag show encrypt box --secret #{wrong_secret}") }.to raise_error(Chef::EncryptedDataBagItem::DecryptionFailure) + end + end + + context "not provided secret key" do + it "shows encrypted data with a warning" do + expect(knife("data bag show encrypt box").stderr).to eq("WARNING: Encrypted data bag detected, but no secret provided for decoding. Displaying encrypted data.\n") + end + end + end + + context "with unencrypted data" do + context "provided secret key" do + it "shows unencrypted data with a warning" do + expect(knife("data bag show rocket falcon9 --secret #{right_secret}").stderr).to eq("WARNING: Unencrypted data bag detected, ignoring any provided secret options.\n") + end + end + + context "not provided secret key" do + it "shows null with an empty data bag" do + knife("data bag show canteloupe").should_succeed "\n" + end + + it "show list of items in a bag" do + knife("data bag show rocket").should_succeed <<~EOM + ariane + atlas + falcon9 + EOM + end + + it "show data of the item" do + knife("data bag show rocket falcon9").should_succeed <<~EOM + heavy: true + id: falcon9 + EOM + end + end + end + end +end diff --git a/knife/spec/integration/delete_spec.rb b/knife/spec/integration/delete_spec.rb new file mode 100644 index 0000000000..e00949e7f4 --- /dev/null +++ b/knife/spec/integration/delete_spec.rb @@ -0,0 +1,1018 @@ +# +# 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/delete" +require "chef/knife/list" +require "chef/knife/raw" + +describe "knife delete", :workstation do + include IntegrationSupport + include KnifeSupport + + let :everything do + <<~EOM + /clients + /clients/x.json + /cookbooks + /cookbooks/x + /cookbooks/x/metadata.rb + /data_bags + /data_bags/x + /data_bags/x/y.json + /environments + /environments/_default.json + /environments/x.json + /nodes + /nodes/x.json + /roles + /roles/x.json + /users + /users/x.json + EOM + end + + let :server_everything do + <<~EOM + /clients + /clients/chef-validator.json + /clients/chef-webui.json + /clients/x.json + /cookbooks + /cookbooks/x + /cookbooks/x/metadata.rb + /data_bags + /data_bags/x + /data_bags/x/y.json + /environments + /environments/_default.json + /environments/x.json + /nodes + /nodes/x.json + /roles + /roles/x.json + /users + /users/admin.json + /users/x.json + EOM + end + let :server_nothing do + <<~EOM + /clients + /clients/chef-validator.json + /clients/chef-webui.json + /cookbooks + /data_bags + /environments + /environments/_default.json + /nodes + /roles + /users + /users/admin.json + EOM + end + + let :nothing do + <<~EOM + /clients + /cookbooks + /data_bags + /environments + /nodes + /roles + /users + EOM + end + + 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 "also has one of each thing" do + before do + file "clients/x.json", {} + file "cookbooks/x/metadata.rb", "" + file "data_bags/x/y.json", {} + file "environments/_default.json", {} + file "environments/x.json", {} + file "nodes/x.json", {} + file "roles/x.json", {} + file "users/x.json", {} + end + + it "knife delete --both /cookbooks/x fails" do + knife("delete --both /cookbooks/x").should_fail <<~EOM + ERROR: /cookbooks/x (remote) must be deleted recursively! Pass -r to knife delete. + ERROR: /cookbooks/x (local) must be deleted recursively! Pass -r to knife delete. + EOM + knife("list -Rf /").should_succeed server_everything + knife("list -Rf --local /").should_succeed everything + end + + it "knife delete --both -r /cookbooks/x deletes x" do + knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n" + knife("list -Rf /").should_succeed <<~EOM + /clients + /clients/chef-validator.json + /clients/chef-webui.json + /clients/x.json + /cookbooks + /data_bags + /data_bags/x + /data_bags/x/y.json + /environments + /environments/_default.json + /environments/x.json + /nodes + /nodes/x.json + /roles + /roles/x.json + /users + /users/admin.json + /users/x.json + EOM + knife("list -Rf --local /").should_succeed <<~EOM + /clients + /clients/x.json + /cookbooks + /data_bags + /data_bags/x + /data_bags/x/y.json + /environments + /environments/_default.json + /environments/x.json + /nodes + /nodes/x.json + /roles + /roles/x.json + /users + /users/x.json + EOM + end + + it "knife delete -r --local /cookbooks/x deletes x locally but not remotely" do + knife("delete -r --local /cookbooks/x").should_succeed "Deleted /cookbooks/x\n" + knife("list -Rf /").should_succeed server_everything + knife("list -Rf --local /").should_succeed <<~EOM + /clients + /clients/x.json + /cookbooks + /data_bags + /data_bags/x + /data_bags/x/y.json + /environments + /environments/_default.json + /environments/x.json + /nodes + /nodes/x.json + /roles + /roles/x.json + /users + /users/x.json + EOM + end + + it "knife delete -r /cookbooks/x deletes x remotely but not locally" do + knife("delete -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n" + knife("list -Rf /").should_succeed <<~EOM + /clients + /clients/chef-validator.json + /clients/chef-webui.json + /clients/x.json + /cookbooks + /data_bags + /data_bags/x + /data_bags/x/y.json + /environments + /environments/_default.json + /environments/x.json + /nodes + /nodes/x.json + /roles + /roles/x.json + /users + /users/admin.json + /users/x.json + EOM + knife("list -Rf --local /").should_succeed everything + end + + # TODO delete empty data bag (particularly different on local side) + context "with an empty data bag on both" do + before do + data_bag "empty", {} + directory "data_bags/empty" + end + + it "knife delete --both /data_bags/empty fails but deletes local version" do + knife("delete --both /data_bags/empty").should_fail <<~EOM + ERROR: /data_bags/empty (remote) must be deleted recursively! Pass -r to knife delete. + ERROR: /data_bags/empty (local) must be deleted recursively! Pass -r to knife delete. + EOM + knife("list -Rf /").should_succeed <<~EOM + /clients + /clients/chef-validator.json + /clients/chef-webui.json + /clients/x.json + /cookbooks + /cookbooks/x + /cookbooks/x/metadata.rb + /data_bags + /data_bags/empty + /data_bags/x + /data_bags/x/y.json + /environments + /environments/_default.json + /environments/x.json + /nodes + /nodes/x.json + /roles + /roles/x.json + /users + /users/admin.json + /users/x.json + EOM + knife("list -Rf --local /").should_succeed <<~EOM + /clients + /clients/x.json + /cookbooks + /cookbooks/x + /cookbooks/x/metadata.rb + /data_bags + /data_bags/empty + /data_bags/x + /data_bags/x/y.json + /environments + /environments/_default.json + /environments/x.json + /nodes + /nodes/x.json + /roles + /roles/x.json + /users + /users/x.json + EOM + end + end + + it "knife delete --both /data_bags/x fails" do + knife("delete --both /data_bags/x").should_fail <<~EOM + ERROR: /data_bags/x (remote) must be deleted recursively! Pass -r to knife delete. + ERROR: /data_bags/x (local) must be deleted recursively! Pass -r to knife delete. + EOM + knife("list -Rf /").should_succeed server_everything + knife("list -Rf --local /").should_succeed everything + end + + it "knife delete --both -r /data_bags/x deletes x" do + knife("delete --both -r /data_bags/x").should_succeed "Deleted /data_bags/x\n" + knife("list -Rf /").should_succeed <<~EOM + /clients + /clients/chef-validator.json + /clients/chef-webui.json + /clients/x.json + /cookbooks + /cookbooks/x + /cookbooks/x/metadata.rb + /data_bags + /environments + /environments/_default.json + /environments/x.json + /nodes + /nodes/x.json + /roles + /roles/x.json + /users + /users/admin.json + /users/x.json + EOM + knife("list -Rf --local /").should_succeed <<~EOM + /clients + /clients/x.json + /cookbooks + /cookbooks/x + /cookbooks/x/metadata.rb + /data_bags + /environments + /environments/_default.json + /environments/x.json + /nodes + /nodes/x.json + /roles + /roles/x.json + /users + /users/x.json + EOM + end + + it "knife delete --both /environments/x.json deletes x" do + knife("delete --both /environments/x.json").should_succeed "Deleted /environments/x.json\n" + knife("list -Rf /").should_succeed <<~EOM + /clients + /clients/chef-validator.json + /clients/chef-webui.json + /clients/x.json + /cookbooks + /cookbooks/x + /cookbooks/x/metadata.rb + /data_bags + /data_bags/x + /data_bags/x/y.json + /environments + /environments/_default.json + /nodes + /nodes/x.json + /roles + /roles/x.json + /users + /users/admin.json + /users/x.json + EOM + knife("list -Rf --local /").should_succeed <<~EOM + /clients + /clients/x.json + /cookbooks + /cookbooks/x + /cookbooks/x/metadata.rb + /data_bags + /data_bags/x + /data_bags/x/y.json + /environments + /environments/_default.json + /nodes + /nodes/x.json + /roles + /roles/x.json + /users + /users/x.json + EOM + end + + it "knife delete --both /roles/x.json deletes x" do + knife("delete --both /roles/x.json").should_succeed "Deleted /roles/x.json\n" + knife("list -Rf /").should_succeed <<~EOM + /clients + /clients/chef-validator.json + /clients/chef-webui.json + /clients/x.json + /cookbooks + /cookbooks/x + /cookbooks/x/metadata.rb + /data_bags + /data_bags/x + /data_bags/x/y.json + /environments + /environments/_default.json + /environments/x.json + /nodes + /nodes/x.json + /roles + /users + /users/admin.json + /users/x.json + EOM + knife("list -Rf --local /").should_succeed <<~EOM + /clients + /clients/x.json + /cookbooks + /cookbooks/x + /cookbooks/x/metadata.rb + /data_bags + /data_bags/x + /data_bags/x/y.json + /environments + /environments/_default.json + /environments/x.json + /nodes + /nodes/x.json + /roles + /users + /users/x.json + EOM + end + + it "knife delete --both /environments/_default.json fails but still deletes the local copy" do + knife("delete --both /environments/_default.json").should_fail stderr: "ERROR: /environments/_default.json (remote) cannot be deleted (default environment cannot be modified).\n", stdout: "Deleted /environments/_default.json\n" + knife("list -Rf /").should_succeed server_everything + knife("list -Rf --local /").should_succeed <<~EOM + /clients + /clients/x.json + /cookbooks + /cookbooks/x + /cookbooks/x/metadata.rb + /data_bags + /data_bags/x + /data_bags/x/y.json + /environments + /environments/x.json + /nodes + /nodes/x.json + /roles + /roles/x.json + /users + /users/x.json + EOM + end + + it "knife delete --both /environments/nonexistent.json fails" do + knife("delete --both /environments/nonexistent.json").should_fail "ERROR: /environments/nonexistent.json: No such file or directory\n" + knife("list -Rf /").should_succeed server_everything + knife("list -Rf --local /").should_succeed everything + end + + it "knife delete --both / fails" do + knife("delete --both /").should_fail <<~EOM + ERROR: / (remote) cannot be deleted. + ERROR: / (local) cannot be deleted. + EOM + knife("list -Rf /").should_succeed server_everything + knife("list -Rf --local /").should_succeed everything + end + + it "knife delete --both -r /* fails" do + knife("delete --both -r /*").should_fail <<~EOM + ERROR: / (remote) cannot be deleted. + ERROR: / (local) cannot be deleted. + ERROR: /clients (remote) cannot be deleted. + ERROR: /clients (local) cannot be deleted. + ERROR: /cookbooks (remote) cannot be deleted. + ERROR: /cookbooks (local) cannot be deleted. + ERROR: /data_bags (remote) cannot be deleted. + ERROR: /data_bags (local) cannot be deleted. + ERROR: /environments (remote) cannot be deleted. + ERROR: /environments (local) cannot be deleted. + ERROR: /nodes (remote) cannot be deleted. + ERROR: /nodes (local) cannot be deleted. + ERROR: /roles (remote) cannot be deleted. + ERROR: /roles (local) cannot be deleted. + ERROR: /users (remote) cannot be deleted. + ERROR: /users (local) cannot be deleted. + EOM + knife("list -Rf /").should_succeed server_everything + knife("list -Rf --local /").should_succeed everything + end + 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 delete --both /cookbooks/x fails" do + knife("delete --both /cookbooks/x").should_fail "ERROR: /cookbooks/x (remote) must be deleted recursively! Pass -r to knife delete.\n" + knife("list -Rf /").should_succeed server_everything + knife("list -Rf --local /").should_succeed nothing + end + + it "knife delete --both -r /cookbooks/x deletes x" do + knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n" + knife("list -Rf /").should_succeed <<~EOM + /clients + /clients/chef-validator.json + /clients/chef-webui.json + /clients/x.json + /cookbooks + /data_bags + /data_bags/x + /data_bags/x/y.json + /environments + /environments/_default.json + /environments/x.json + /nodes + /nodes/x.json + /roles + /roles/x.json + /users + /users/admin.json + /users/x.json + EOM + knife("list -Rf --local /").should_succeed nothing + end + + it "knife delete --both /data_bags/x fails" do + knife("delete --both /data_bags/x").should_fail "ERROR: /data_bags/x (remote) must be deleted recursively! Pass -r to knife delete.\n" + knife("list -Rf /").should_succeed server_everything + knife("list -Rf --local /").should_succeed nothing + end + + it "knife delete --both -r /data_bags/x deletes x" do + knife("delete --both -r /data_bags/x").should_succeed "Deleted /data_bags/x\n" + knife("list -Rf /").should_succeed <<~EOM + /clients + /clients/chef-validator.json + /clients/chef-webui.json + /clients/x.json + /cookbooks + /cookbooks/x + /cookbooks/x/metadata.rb + /data_bags + /environments + /environments/_default.json + /environments/x.json + /nodes + /nodes/x.json + /roles + /roles/x.json + /users + /users/admin.json + /users/x.json + EOM + knife("list -Rf --local /").should_succeed nothing + end + + it "knife delete --both /environments/x.json deletes x" do + knife("delete --both /environments/x.json").should_succeed "Deleted /environments/x.json\n" + knife("list -Rf /").should_succeed <<~EOM + /clients + /clients/chef-validator.json + /clients/chef-webui.json + /clients/x.json + /cookbooks + /cookbooks/x + /cookbooks/x/metadata.rb + /data_bags + /data_bags/x + /data_bags/x/y.json + /environments + /environments/_default.json + /nodes + /nodes/x.json + /roles + /roles/x.json + /users + /users/admin.json + /users/x.json + EOM + knife("list -Rf --local /").should_succeed nothing + end + + it "knife delete --both /roles/x.json deletes x" do + knife("delete --both /roles/x.json").should_succeed "Deleted /roles/x.json\n" + knife("list -Rf /").should_succeed <<~EOM + /clients + /clients/chef-validator.json + /clients/chef-webui.json + /clients/x.json + /cookbooks + /cookbooks/x + /cookbooks/x/metadata.rb + /data_bags + /data_bags/x + /data_bags/x/y.json + /environments + /environments/_default.json + /environments/x.json + /nodes + /nodes/x.json + /roles + /users + /users/admin.json + /users/x.json + EOM + knife("list -Rf --local /").should_succeed nothing + end + + it "knife delete --both /environments/_default.json fails" do + knife("delete --both /environments/_default.json").should_fail "", stderr: "ERROR: /environments/_default.json (remote) cannot be deleted (default environment cannot be modified).\n" + knife("list -Rf /").should_succeed server_everything + knife("list -Rf --local /").should_succeed nothing + end + + it "knife delete --both / fails" do + knife("delete --both /").should_fail "ERROR: / (remote) cannot be deleted.\nERROR: / (local) cannot be deleted.\n" + knife("list -Rf /").should_succeed server_everything + knife("list -Rf --local /").should_succeed nothing + end + + it "knife delete --both -r /* fails" do + knife("delete --both -r /*").should_fail <<~EOM + ERROR: / (remote) cannot be deleted. + ERROR: / (local) cannot be deleted. + ERROR: /clients (remote) cannot be deleted. + ERROR: /clients (local) cannot be deleted. + ERROR: /cookbooks (remote) cannot be deleted. + ERROR: /cookbooks (local) cannot be deleted. + ERROR: /data_bags (remote) cannot be deleted. + ERROR: /data_bags (local) cannot be deleted. + ERROR: /environments (remote) cannot be deleted. + ERROR: /environments (local) cannot be deleted. + ERROR: /nodes (remote) cannot be deleted. + ERROR: /nodes (local) cannot be deleted. + ERROR: /roles (remote) cannot be deleted. + ERROR: /roles (local) cannot be deleted. + ERROR: /users (remote) cannot be deleted. + ERROR: /users (local) cannot be deleted. + EOM + knife("list -Rf /").should_succeed server_everything + knife("list -Rf --local /").should_succeed nothing + end + + it "knife delete --both /environments/nonexistent.json fails" do + knife("delete --both /environments/nonexistent.json").should_fail "ERROR: /environments/nonexistent.json: No such file or directory\n" + knife("list -Rf /").should_succeed server_everything + knife("list -Rf --local /").should_succeed nothing + end + + context "and cwd is at the top level" do + before { cwd "." } + it "knife delete fails" do + knife("delete").should_fail "FATAL: You must specify at least one argument. If you want to delete everything in this directory, run \"knife delete --recurse .\"\n", stdout: /USAGE/ + knife("list -Rf /").should_succeed <<~EOM + clients + clients/chef-validator.json + clients/chef-webui.json + clients/x.json + cookbooks + cookbooks/x + cookbooks/x/metadata.rb + data_bags + data_bags/x + data_bags/x/y.json + environments + environments/_default.json + environments/x.json + nodes + nodes/x.json + roles + roles/x.json + users + users/admin.json + users/x.json + EOM + knife("list -Rf --local /").should_succeed <<~EOM + clients + cookbooks + data_bags + environments + nodes + roles + users + EOM + end + end + end + end + + when_the_chef_server "is empty" do + when_the_repository "has one of each thing" do + before do + file "clients/x.json", {} + file "cookbooks/x/metadata.rb", "" + file "data_bags/x/y.json", {} + file "environments/_default.json", {} + file "environments/x.json", {} + file "nodes/x.json", {} + file "roles/x.json", {} + file "users/x.json", {} + end + + it "knife delete --both /cookbooks/x fails" do + knife("delete --both /cookbooks/x").should_fail "ERROR: /cookbooks/x (local) must be deleted recursively! Pass -r to knife delete.\n" + knife("list -Rf /").should_succeed server_nothing + knife("list -Rf --local /").should_succeed everything + end + + it "knife delete --both -r /cookbooks/x deletes x" do + knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n" + knife("list -Rf /").should_succeed server_nothing + knife("list -Rf --local /").should_succeed <<~EOM + /clients + /clients/x.json + /cookbooks + /data_bags + /data_bags/x + /data_bags/x/y.json + /environments + /environments/_default.json + /environments/x.json + /nodes + /nodes/x.json + /roles + /roles/x.json + /users + /users/x.json + EOM + end + + it "knife delete --both /data_bags/x fails" do + knife("delete --both /data_bags/x").should_fail "ERROR: /data_bags/x (local) must be deleted recursively! Pass -r to knife delete.\n" + knife("list -Rf /").should_succeed server_nothing + knife("list -Rf --local /").should_succeed everything + end + + it "knife delete --both -r /data_bags/x deletes x" do + knife("delete --both -r /data_bags/x").should_succeed "Deleted /data_bags/x\n" + knife("list -Rf /").should_succeed server_nothing + knife("list -Rf --local /").should_succeed <<~EOM + /clients + /clients/x.json + /cookbooks + /cookbooks/x + /cookbooks/x/metadata.rb + /data_bags + /environments + /environments/_default.json + /environments/x.json + /nodes + /nodes/x.json + /roles + /roles/x.json + /users + /users/x.json + EOM + end + + it "knife delete --both /environments/x.json deletes x" do + knife("delete --both /environments/x.json").should_succeed "Deleted /environments/x.json\n" + knife("list -Rf /").should_succeed server_nothing + knife("list -Rf --local /").should_succeed <<~EOM + /clients + /clients/x.json + /cookbooks + /cookbooks/x + /cookbooks/x/metadata.rb + /data_bags + /data_bags/x + /data_bags/x/y.json + /environments + /environments/_default.json + /nodes + /nodes/x.json + /roles + /roles/x.json + /users + /users/x.json + EOM + end + + it "knife delete --both /roles/x.json deletes x" do + knife("delete --both /roles/x.json").should_succeed "Deleted /roles/x.json\n" + knife("list -Rf /").should_succeed server_nothing + knife("list -Rf --local /").should_succeed <<~EOM + /clients + /clients/x.json + /cookbooks + /cookbooks/x + /cookbooks/x/metadata.rb + /data_bags + /data_bags/x + /data_bags/x/y.json + /environments + /environments/_default.json + /environments/x.json + /nodes + /nodes/x.json + /roles + /users + /users/x.json + EOM + end + + it "knife delete --both /environments/_default.json fails but still deletes the local copy" do + knife("delete --both /environments/_default.json").should_fail stderr: "ERROR: /environments/_default.json (remote) cannot be deleted (default environment cannot be modified).\n", stdout: "Deleted /environments/_default.json\n" + knife("list -Rf /").should_succeed server_nothing + knife("list -Rf --local /").should_succeed <<~EOM + /clients + /clients/x.json + /cookbooks + /cookbooks/x + /cookbooks/x/metadata.rb + /data_bags + /data_bags/x + /data_bags/x/y.json + /environments + /environments/x.json + /nodes + /nodes/x.json + /roles + /roles/x.json + /users + /users/x.json + EOM + end + + it "knife delete --both / fails" do + knife("delete --both /").should_fail "ERROR: / (remote) cannot be deleted.\nERROR: / (local) cannot be deleted.\n" + knife("list -Rf /").should_succeed server_nothing + knife("list -Rf --local /").should_succeed everything + end + + it "knife delete --both -r /* fails" do + knife("delete --both -r /*").should_fail <<~EOM + ERROR: / (remote) cannot be deleted. + ERROR: / (local) cannot be deleted. + ERROR: /clients (remote) cannot be deleted. + ERROR: /clients (local) cannot be deleted. + ERROR: /cookbooks (remote) cannot be deleted. + ERROR: /cookbooks (local) cannot be deleted. + ERROR: /data_bags (remote) cannot be deleted. + ERROR: /data_bags (local) cannot be deleted. + ERROR: /environments (remote) cannot be deleted. + ERROR: /environments (local) cannot be deleted. + ERROR: /nodes (remote) cannot be deleted. + ERROR: /nodes (local) cannot be deleted. + ERROR: /roles (remote) cannot be deleted. + ERROR: /roles (local) cannot be deleted. + ERROR: /users (remote) cannot be deleted. + ERROR: /users (local) cannot be deleted. + EOM + knife("list -Rf /").should_succeed server_nothing + knife("list -Rf --local /").should_succeed everything + end + + it "knife delete --both /environments/nonexistent.json fails" do + knife("delete --both /environments/nonexistent.json").should_fail "ERROR: /environments/nonexistent.json: No such file or directory\n" + knife("list -Rf /").should_succeed server_nothing + knife("list -Rf --local /").should_succeed everything + end + + context "and cwd is at the top level" do + before { cwd "." } + it "knife delete fails" do + knife("delete").should_fail "FATAL: You must specify at least one argument. If you want to delete everything in this directory, run \"knife delete --recurse .\"\n", stdout: /USAGE/ + knife("list -Rf /").should_succeed <<~EOM + clients + clients/chef-validator.json + clients/chef-webui.json + cookbooks + data_bags + environments + environments/_default.json + nodes + roles + users + users/admin.json + EOM + knife("list -Rf --local /").should_succeed <<~EOM + clients + clients/x.json + cookbooks + cookbooks/x + cookbooks/x/metadata.rb + data_bags + data_bags/x + data_bags/x/y.json + environments + environments/_default.json + environments/x.json + nodes + nodes/x.json + roles + roles/x.json + users + users/x.json + EOM + end + end + end + end + + when_the_repository "has a cookbook" do + before do + file "cookbooks/x/metadata.rb", '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 + + # TODO this seems wrong + it "knife delete --both -r /cookbooks/x deletes the latest version on the server and the local version" do + knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n" + knife("raw /cookbooks/x").should_succeed(/1.0.0/) + knife("list --local /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 delete --both /cookbooks/x deletes the latest version on the server and the local version" do + knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n" + knife("raw /cookbooks/x").should_succeed(/0.9.9/) + knife("list --local /cookbooks").should_succeed "" + end + end + + when_the_chef_server "has a later version for the cookbook, and no current version" do + before { cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" } } + + it "knife delete --both /cookbooks/x deletes the server and client version of the cookbook" do + knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n" + knife("raw /cookbooks/x").should_fail(/404/) + knife("list --local /cookbooks").should_succeed "" + end + end + + when_the_chef_server "has an earlier version for the cookbook, and no current version" do + before { cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" } } + + it "knife delete --both /cookbooks/x deletes the server and client version of the cookbook" do + knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n" + knife("raw /cookbooks/x").should_fail(/404/) + knife("list --local /cookbooks").should_succeed "" + end + end + end + + when_the_repository "is empty" do + when_the_chef_server "has two versions of a cookbook" do + before do + cookbook "x", "2.0.11" + cookbook "x", "11.0.0" + end + + it "knife delete deletes the latest version" do + knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n" + knife("raw /cookbooks/x").should_succeed( /2.0.11/ ) + end + end + end + + when_the_chef_server "is in Enterprise mode", osc_compat: false, single_org: false do + before do + organization "foo" do + container "x", {} + group "x", {} + policy "x", "1.2.3", {} + policy_group "x", { "policies" => { "x" => { "revision_id" => "1.2.3" } } } + end + end + + before :each do + Chef::Config.chef_server_url = URI.join(Chef::Config.chef_server_url, "/organizations/foo") + end + + it "knife delete /acls/containers/environments.json fails with a reasonable error" do + knife("delete /acls/containers/environments.json").should_fail "ERROR: /acls/containers/environments.json (remote) ACLs cannot be deleted.\n" + end + + it "knife delete /containers/x.json succeeds" do + knife("delete /containers/x.json").should_succeed "Deleted /containers/x.json\n" + knife("raw /containers/x.json").should_fail(/404/) + end + + it "knife delete /groups/x.json succeeds" do + knife("delete /groups/x.json").should_succeed "Deleted /groups/x.json\n" + knife("raw /groups/x.json").should_fail(/404/) + end + + it "knife delete /policies/x-1.2.3.json succeeds" do + knife("raw /policies/x/revisions/1.2.3").should_succeed "{\n \"name\": \"x\",\n \"revision_id\": \"1.2.3\",\n \"run_list\": [\n\n ],\n \"cookbook_locks\": {\n\n }\n}\n" + knife("delete /policies/x-1.2.3.json").should_succeed "Deleted /policies/x-1.2.3.json\n" + knife("raw /policies/x/revisions/1.2.3").should_fail(/404/) + end + + it "knife delete /policy_groups/x.json succeeds" do + knife("raw /policy_groups/x").should_succeed "{\n \"uri\": \"http://127.0.0.1:8900/organizations/foo/policy_groups/x\",\n \"policies\": {\n \"x\": {\n \"revision_id\": \"1.2.3\"\n }\n }\n}\n" + knife("delete /policy_groups/x.json").should_succeed "Deleted /policy_groups/x.json\n" + knife("raw /policy_groups/x").should_fail(/404/) + end + + it "knife delete /org.json fails with a reasonable error" do + knife("delete /org.json").should_fail "ERROR: /org.json (remote) cannot be deleted.\n" + end + + it "knife delete /invitations.json fails with a reasonable error" do + knife("delete /invitations.json").should_fail "ERROR: /invitations.json (remote) cannot be deleted.\n" + end + + it "knife delete /members.json fails with a reasonable error" do + knife("delete /members.json").should_fail "ERROR: /members.json (remote) cannot be deleted.\n" + end + end +end diff --git a/knife/spec/integration/deps_spec.rb b/knife/spec/integration/deps_spec.rb new file mode 100644 index 0000000000..9875277f14 --- /dev/null +++ b/knife/spec/integration/deps_spec.rb @@ -0,0 +1,703 @@ +# +# 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 "support/shared/context/config" +require "chef/knife/deps" + +describe "knife deps", :workstation do + include IntegrationSupport + include KnifeSupport + + context "local" do + when_the_repository "has a role with no run_list" do + before { file "roles/starring.json", {} } + it "knife deps reports no dependencies" do + knife("deps /roles/starring.json").should_succeed "/roles/starring.json\n" + end + end + + when_the_repository "has a role with a default run_list" do + before do + file "roles/starring.json", { "run_list" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} } + file "roles/minor.json", {} + file "cookbooks/quiche/metadata.rb", 'name "quiche"' + file "cookbooks/quiche/recipes/default.rb", "" + file "cookbooks/soup/metadata.rb", 'name "soup"' + file "cookbooks/soup/recipes/chicken.rb", "" + end + it "knife deps reports all dependencies" do + knife("deps /roles/starring.json").should_succeed <<~EOM + /roles/minor.json + /cookbooks/quiche + /cookbooks/soup + /roles/starring.json + EOM + end + end + + when_the_repository "has a role with an env_run_list" do + before do + file "roles/starring.json", { "env_run_lists" => { "desert" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} } } + file "roles/minor.json", {} + file "cookbooks/quiche/metadata.rb", 'name "quiche"' + file "cookbooks/quiche/recipes/default.rb", "" + file "cookbooks/soup/metadata.rb", 'name "soup"' + file "cookbooks/soup/recipes/chicken.rb", "" + end + it "knife deps reports all dependencies" do + knife("deps /roles/starring.json").should_succeed <<~EOM + /roles/minor.json + /cookbooks/quiche + /cookbooks/soup + /roles/starring.json + EOM + end + end + + when_the_repository "has a node with no environment or run_list" do + before { file "nodes/mort.json", {} } + it "knife deps reports just the node" do + knife("deps /nodes/mort.json").should_succeed "/nodes/mort.json\n" + end + end + when_the_repository "has a node with an environment" do + before do + file "environments/desert.json", {} + file "nodes/mort.json", { "chef_environment" => "desert" } + end + it "knife deps reports just the node" do + knife("deps /nodes/mort.json").should_succeed "/environments/desert.json\n/nodes/mort.json\n" + end + end + when_the_repository "has a node with roles and recipes in its run_list" do + before do + file "roles/minor.json", {} + file "cookbooks/quiche/metadata.rb", 'name "quiche"' + file "cookbooks/quiche/recipes/default.rb", "" + file "cookbooks/soup/metadata.rb", 'name "soup"' + file "cookbooks/soup/recipes/chicken.rb", "" + file "nodes/mort.json", { "run_list" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} } + end + it "knife deps reports just the node" do + knife("deps /nodes/mort.json").should_succeed <<~EOM + /roles/minor.json + /cookbooks/quiche + /cookbooks/soup + /nodes/mort.json + EOM + end + end + when_the_repository "has a cookbook with no dependencies" do + before do + file "cookbooks/quiche/metadata.rb", 'name "quiche"' + file "cookbooks/quiche/recipes/default.rb", "" + end + it "knife deps reports just the cookbook" do + knife("deps /cookbooks/quiche").should_succeed "/cookbooks/quiche\n" + end + end + when_the_repository "has a cookbook with dependencies" do + before do + file "cookbooks/kettle/metadata.rb", 'name "kettle"' + file "cookbooks/quiche/metadata.rb", 'name "quiche" +depends "kettle"' + file "cookbooks/quiche/recipes/default.rb", "" + end + it "knife deps reports just the cookbook" do + knife("deps /cookbooks/quiche").should_succeed "/cookbooks/kettle\n/cookbooks/quiche\n" + end + end + when_the_repository "has a data bag" do + before { file "data_bags/bag/item.json", {} } + it "knife deps reports just the data bag" do + knife("deps /data_bags/bag/item.json").should_succeed "/data_bags/bag/item.json\n" + end + end + when_the_repository "has an environment" do + before { file "environments/desert.json", {} } + it "knife deps reports just the environment" do + knife("deps /environments/desert.json").should_succeed "/environments/desert.json\n" + end + end + when_the_repository "has a deep dependency tree" do + before do + file "roles/starring.json", { "run_list" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} } + file "roles/minor.json", {} + file "cookbooks/quiche/metadata.rb", 'name "quiche"' + file "cookbooks/quiche/recipes/default.rb", "" + file "cookbooks/soup/metadata.rb", 'name "soup"' + file "cookbooks/soup/recipes/chicken.rb", "" + file "environments/desert.json", {} + file "nodes/mort.json", { "chef_environment" => "desert", "run_list" => [ "role[starring]" ] } + file "nodes/bart.json", { "run_list" => [ "role[minor]" ] } + end + + it "knife deps reports all dependencies" do + knife("deps /nodes/mort.json").should_succeed <<~EOM + /environments/desert.json + /roles/minor.json + /cookbooks/quiche + /cookbooks/soup + /roles/starring.json + /nodes/mort.json + EOM + end + it "knife deps * reports all dependencies of all things" do + knife("deps /nodes/*").should_succeed <<~EOM + /roles/minor.json + /nodes/bart.json + /environments/desert.json + /cookbooks/quiche + /cookbooks/soup + /roles/starring.json + /nodes/mort.json + EOM + end + it "knife deps a b reports all dependencies of a and b" do + knife("deps /nodes/bart.json /nodes/mort.json").should_succeed <<~EOM + /roles/minor.json + /nodes/bart.json + /environments/desert.json + /cookbooks/quiche + /cookbooks/soup + /roles/starring.json + /nodes/mort.json + EOM + end + it "knife deps --tree /* shows dependencies in a tree" do + knife("deps --tree /nodes/*").should_succeed <<~EOM + /nodes/bart.json + /roles/minor.json + /nodes/mort.json + /environments/desert.json + /roles/starring.json + /roles/minor.json + /cookbooks/quiche + /cookbooks/soup + EOM + end + it "knife deps --tree --no-recurse shows only the first level of dependencies" do + knife("deps --tree --no-recurse /nodes/*").should_succeed <<~EOM + /nodes/bart.json + /roles/minor.json + /nodes/mort.json + /environments/desert.json + /roles/starring.json + EOM + end + end + + context "circular dependencies" do + when_the_repository "has cookbooks with circular dependencies" do + before do + file "cookbooks/foo/metadata.rb", 'name "foo" +depends "bar"' + file "cookbooks/bar/metadata.rb", 'name "bar" +depends "baz"' + file "cookbooks/baz/metadata.rb", 'name "baz" +depends "foo"' + end + + it "knife deps prints each once" do + knife("deps /cookbooks/foo").should_succeed( + stdout: "/cookbooks/baz\n/cookbooks/bar\n/cookbooks/foo\n" + ) + end + it "knife deps --tree prints each once" do + knife("deps --tree /cookbooks/foo").should_succeed( + stdout: "/cookbooks/foo\n /cookbooks/bar\n /cookbooks/baz\n /cookbooks/foo\n" + ) + end + end + when_the_repository "has roles with circular dependencies" do + before do + file "roles/foo.json", { "run_list" => [ "role[bar]" ] } + file "roles/bar.json", { "run_list" => [ "role[baz]" ] } + file "roles/baz.json", { "run_list" => [ "role[foo]" ] } + file "roles/self.json", { "run_list" => [ "role[self]" ] } + end + it "knife deps prints each once" do + knife("deps /roles/foo.json /roles/self.json").should_succeed <<~EOM + /roles/baz.json + /roles/bar.json + /roles/foo.json + /roles/self.json + EOM + end + it "knife deps --tree prints each once" do + knife("deps --tree /roles/foo.json /roles/self.json") do + expect(stdout).to eq("/roles/foo.json\n /roles/bar.json\n /roles/baz.json\n /roles/foo.json\n/roles/self.json\n /roles/self.json\n") + expect(stderr).to eq("WARNING: No knife configuration file found. See https://docs.chef.io/config_rb/ for details.\n") + end + end + end + end + + context "missing objects" do + when_the_repository "is empty" do + it "knife deps /blah reports an error" do + knife("deps /blah").should_fail( + exit_code: 2, + stdout: "/blah\n", + stderr: "ERROR: /blah: No such file or directory\n" + ) + end + it "knife deps /roles/x.json reports an error" do + knife("deps /roles/x.json").should_fail( + exit_code: 2, + stdout: "/roles/x.json\n", + stderr: "ERROR: /roles/x.json: No such file or directory\n" + ) + end + it "knife deps /nodes/x.json reports an error" do + knife("deps /nodes/x.json").should_fail( + exit_code: 2, + stdout: "/nodes/x.json\n", + stderr: "ERROR: /nodes/x.json: No such file or directory\n" + ) + end + it "knife deps /environments/x.json reports an error" do + knife("deps /environments/x.json").should_fail( + exit_code: 2, + stdout: "/environments/x.json\n", + stderr: "ERROR: /environments/x.json: No such file or directory\n" + ) + end + it "knife deps /cookbooks/x reports an error" do + knife("deps /cookbooks/x").should_fail( + exit_code: 2, + stdout: "/cookbooks/x\n", + stderr: "ERROR: /cookbooks/x: No such file or directory\n" + ) + end + it "knife deps /data_bags/bag/item.json reports an error" do + knife("deps /data_bags/bag/item.json").should_fail( + exit_code: 2, + stdout: "/data_bags/bag/item.json\n", + stderr: "ERROR: /data_bags/bag/item.json: No such file or directory\n" + ) + end + end + when_the_repository "is missing a dependent cookbook" do + before do + file "roles/starring.json", { "run_list" => [ "recipe[quiche]"] } + end + it "knife deps reports the cookbook, along with an error" do + knife("deps /roles/starring.json").should_fail( + exit_code: 2, + stdout: "/cookbooks/quiche\n/roles/starring.json\n", + stderr: "ERROR: /cookbooks/quiche: No such file or directory\n" + ) + end + end + when_the_repository "is missing a dependent environment" do + before do + file "nodes/mort.json", { "chef_environment" => "desert" } + end + it "knife deps reports the environment, along with an error" do + knife("deps /nodes/mort.json").should_fail( + exit_code: 2, + stdout: "/environments/desert.json\n/nodes/mort.json\n", + stderr: "ERROR: /environments/desert.json: No such file or directory\n" + ) + end + end + when_the_repository "is missing a dependent role" do + before do + file "roles/starring.json", { "run_list" => [ "role[minor]"] } + end + it "knife deps reports the role, along with an error" do + knife("deps /roles/starring.json").should_fail( + exit_code: 2, + stdout: "/roles/minor.json\n/roles/starring.json\n", + stderr: "ERROR: /roles/minor.json: No such file or directory\n" + ) + end + end + end + context "invalid objects" do + when_the_repository "is empty" do + it "knife deps / reports itself only" do + knife("deps /").should_succeed("/\n") + end + it "knife deps /roles reports an error" do + knife("deps /roles").should_fail( + exit_code: 2, + stderr: "ERROR: /roles: No such file or directory\n", + stdout: "/roles\n" + ) + end + end + when_the_repository "has a data bag" do + before { file "data_bags/bag/item.json", "" } + it "knife deps /data_bags/bag shows no dependencies" do + knife("deps /data_bags/bag").should_succeed("/data_bags/bag\n") + end + end + when_the_repository "has a cookbook" do + before { file "cookbooks/blah/metadata.rb", 'name "blah"' } + it "knife deps on a cookbook file shows no dependencies" do + knife("deps /cookbooks/blah/metadata.rb").should_succeed( + "/cookbooks/blah/metadata.rb\n" + ) + end + end + end + end + + context "remote" do + include_context "default config options" + + when_the_chef_server "has a role with no run_list" do + before { role "starring", {} } + it "knife deps reports no dependencies" do + knife("deps --remote /roles/starring.json").should_succeed "/roles/starring.json\n" + end + end + + when_the_chef_server "has a role with a default run_list" do + before do + role "starring", { "run_list" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} } + role "minor", {} + cookbook "quiche", "1.0.0", { "metadata.rb" => %Q{name "quiche"\nversion "1.0.0"\n}, "recipes" => { "default.rb" => "" } } + cookbook "soup", "1.0.0", { "metadata.rb" => %Q{name "soup"\nversion "1.0.0"\n}, "recipes" => { "chicken.rb" => "" } } + end + it "knife deps reports all dependencies" do + knife("deps --remote /roles/starring.json").should_succeed <<~EOM + /roles/minor.json + /cookbooks/quiche + /cookbooks/soup + /roles/starring.json + EOM + end + end + + when_the_chef_server "has a role with an env_run_list" do + before do + role "starring", { "env_run_lists" => { "desert" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} } } + role "minor", {} + cookbook "quiche", "1.0.0", { "metadata.rb" => %Q{name "quiche"\nversion "1.0.0"\n}, "recipes" => { "default.rb" => "" } } + cookbook "soup", "1.0.0", { "metadata.rb" => %Q{name "soup"\nversion "1.0.0"\n}, "recipes" => { "chicken.rb" => "" } } + end + it "knife deps reports all dependencies" do + knife("deps --remote /roles/starring.json").should_succeed <<~EOM + /roles/minor.json + /cookbooks/quiche + /cookbooks/soup + /roles/starring.json + EOM + end + end + + when_the_chef_server "has a node with no environment or run_list" do + before { node "mort", {} } + it "knife deps reports just the node" do + knife("deps --remote /nodes/mort.json").should_succeed "/nodes/mort.json\n" + end + end + when_the_chef_server "has a node with an environment" do + before do + environment "desert", {} + node "mort", { "chef_environment" => "desert" } + end + it "knife deps reports just the node" do + knife("deps --remote /nodes/mort.json").should_succeed "/environments/desert.json\n/nodes/mort.json\n" + end + end + when_the_chef_server "has a node with roles and recipes in its run_list" do + before do + role "minor", {} + cookbook "quiche", "1.0.0", { "metadata.rb" => %Q{name "quiche"\nversion "1.0.0"\n}, "recipes" => { "default.rb" => "" } } + cookbook "soup", "1.0.0", { "metadata.rb" => %Q{name "soup"\nversion "1.0.0"\n}, "recipes" => { "chicken.rb" => "" } } + node "mort", { "run_list" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} } + end + it "knife deps reports just the node" do + knife("deps --remote /nodes/mort.json").should_succeed <<~EOM + /roles/minor.json + /cookbooks/quiche + /cookbooks/soup + /nodes/mort.json + EOM + end + end + when_the_chef_server "has a cookbook with no dependencies" do + before do + cookbook "quiche", "1.0.0", { "metadata.rb" => %Q{name "quiche"\nversion "1.0.0"\n}, "recipes" => { "default.rb" => "" } } + end + it "knife deps reports just the cookbook" do + knife("deps --remote /cookbooks/quiche").should_succeed "/cookbooks/quiche\n" + end + end + when_the_chef_server "has a cookbook with dependencies" do + before do + cookbook "kettle", "1.0.0", { "metadata.rb" => %Q{name "kettle"\nversion "1.0.0"\n} } + cookbook "quiche", "1.0.0", { "metadata.rb" => 'name "quiche" +depends "kettle"', "recipes" => { "default.rb" => "" } } + end + it "knife deps reports the cookbook and its dependencies" do + knife("deps --remote /cookbooks/quiche").should_succeed "/cookbooks/kettle\n/cookbooks/quiche\n" + end + end + when_the_chef_server "has a data bag" do + before { data_bag "bag", { "item" => {} } } + it "knife deps reports just the data bag" do + knife("deps --remote /data_bags/bag/item.json").should_succeed "/data_bags/bag/item.json\n" + end + end + when_the_chef_server "has an environment" do + before { environment "desert", {} } + it "knife deps reports just the environment" do + knife("deps --remote /environments/desert.json").should_succeed "/environments/desert.json\n" + end + end + when_the_chef_server "has a deep dependency tree" do + before do + role "starring", { "run_list" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} } + role "minor", {} + cookbook "quiche", "1.0.0", { "metadata.rb" => %Q{name "quiche"\nversion "1.0.0"\n}, "recipes" => { "default.rb" => "" } } + cookbook "soup", "1.0.0", { "metadata.rb" => %Q{name "soup"\nversion "1.0.0"\n}, "recipes" => { "chicken.rb" => "" } } + environment "desert", {} + node "mort", { "chef_environment" => "desert", "run_list" => [ "role[starring]" ] } + node "bart", { "run_list" => [ "role[minor]" ] } + end + + it "knife deps reports all dependencies" do + knife("deps --remote /nodes/mort.json").should_succeed <<~EOM + /environments/desert.json + /roles/minor.json + /cookbooks/quiche + /cookbooks/soup + /roles/starring.json + /nodes/mort.json + EOM + end + it "knife deps * reports all dependencies of all things" do + knife("deps --remote /nodes/*").should_succeed <<~EOM + /roles/minor.json + /nodes/bart.json + /environments/desert.json + /cookbooks/quiche + /cookbooks/soup + /roles/starring.json + /nodes/mort.json + EOM + end + it "knife deps a b reports all dependencies of a and b" do + knife("deps --remote /nodes/bart.json /nodes/mort.json").should_succeed <<~EOM + /roles/minor.json + /nodes/bart.json + /environments/desert.json + /cookbooks/quiche + /cookbooks/soup + /roles/starring.json + /nodes/mort.json + EOM + end + it "knife deps --tree /* shows dependencies in a tree" do + knife("deps --remote --tree /nodes/*").should_succeed <<~EOM + /nodes/bart.json + /roles/minor.json + /nodes/mort.json + /environments/desert.json + /roles/starring.json + /roles/minor.json + /cookbooks/quiche + /cookbooks/soup + EOM + end + it "knife deps --tree --no-recurse shows only the first level of dependencies" do + knife("deps --remote --tree --no-recurse /nodes/*").should_succeed <<~EOM + /nodes/bart.json + /roles/minor.json + /nodes/mort.json + /environments/desert.json + /roles/starring.json + EOM + end + end + + context "circular dependencies" do + when_the_chef_server "has cookbooks with circular dependencies" do + before do + cookbook "foo", "1.0.0", { "metadata.rb" => 'name "foo" +depends "bar"' } + cookbook "bar", "1.0.0", { "metadata.rb" => 'name "bar" +depends "baz"' } + cookbook "baz", "1.0.0", { "metadata.rb" => 'name "baz" +depends "foo"' } + cookbook "self", "1.0.0", { "metadata.rb" => 'name "self" +depends "self"' } + end + it "knife deps prints each once" do + knife("deps --remote /cookbooks/foo /cookbooks/self").should_succeed <<~EOM + /cookbooks/baz + /cookbooks/bar + /cookbooks/foo + /cookbooks/self + EOM + end + it "knife deps --tree prints each once" do + knife("deps --remote --tree /cookbooks/foo /cookbooks/self").should_succeed <<~EOM + /cookbooks/foo + /cookbooks/bar + /cookbooks/baz + /cookbooks/foo + /cookbooks/self + /cookbooks/self + EOM + end + end + when_the_chef_server "has roles with circular dependencies" do + before do + role "foo", { "run_list" => [ "role[bar]" ] } + role "bar", { "run_list" => [ "role[baz]" ] } + role "baz", { "run_list" => [ "role[foo]" ] } + role "self", { "run_list" => [ "role[self]" ] } + end + it "knife deps prints each once" do + knife("deps --remote /roles/foo.json /roles/self.json").should_succeed <<~EOM + /roles/baz.json + /roles/bar.json + /roles/foo.json + /roles/self.json + EOM + end + it "knife deps --tree prints each once" do + knife("deps --remote --tree /roles/foo.json /roles/self.json") do + expect(stdout).to eq("/roles/foo.json\n /roles/bar.json\n /roles/baz.json\n /roles/foo.json\n/roles/self.json\n /roles/self.json\n") + expect(stderr).to eq("WARNING: No knife configuration file found. See https://docs.chef.io/config_rb/ for details.\n") + end + end + end + end + + context "missing objects" do + when_the_chef_server "is empty" do + it "knife deps /blah reports an error" do + knife("deps --remote /blah").should_fail( + exit_code: 2, + stdout: "/blah\n", + stderr: "ERROR: /blah: No such file or directory\n" + ) + end + it "knife deps /roles/x.json reports an error" do + knife("deps --remote /roles/x.json").should_fail( + exit_code: 2, + stdout: "/roles/x.json\n", + stderr: "ERROR: /roles/x.json: No such file or directory\n" + ) + end + it "knife deps /nodes/x.json reports an error" do + knife("deps --remote /nodes/x.json").should_fail( + exit_code: 2, + stdout: "/nodes/x.json\n", + stderr: "ERROR: /nodes/x.json: No such file or directory\n" + ) + end + it "knife deps /environments/x.json reports an error" do + knife("deps --remote /environments/x.json").should_fail( + exit_code: 2, + stdout: "/environments/x.json\n", + stderr: "ERROR: /environments/x.json: No such file or directory\n" + ) + end + it "knife deps /cookbooks/x reports an error" do + knife("deps --remote /cookbooks/x").should_fail( + exit_code: 2, + stdout: "/cookbooks/x\n", + stderr: "ERROR: /cookbooks/x: No such file or directory\n" + ) + end + it "knife deps /data_bags/bag/item reports an error" do + knife("deps --remote /data_bags/bag/item.json").should_fail( + exit_code: 2, + stdout: "/data_bags/bag/item.json\n", + stderr: "ERROR: /data_bags/bag/item.json: No such file or directory\n" + ) + end + end + when_the_chef_server "is missing a dependent cookbook" do + before do + role "starring", { "run_list" => [ "recipe[quiche]"] } + end + it "knife deps reports the cookbook, along with an error" do + knife("deps --remote /roles/starring.json").should_fail( + exit_code: 2, + stdout: "/cookbooks/quiche\n/roles/starring.json\n", + stderr: "ERROR: /cookbooks/quiche: No such file or directory\n" + ) + end + end + when_the_chef_server "is missing a dependent environment" do + before do + node "mort", { "chef_environment" => "desert" } + end + it "knife deps reports the environment, along with an error" do + knife("deps --remote /nodes/mort.json").should_fail( + exit_code: 2, + stdout: "/environments/desert.json\n/nodes/mort.json\n", + stderr: "ERROR: /environments/desert.json: No such file or directory\n" + ) + end + end + when_the_chef_server "is missing a dependent role" do + before do + role "starring", { "run_list" => [ "role[minor]"] } + end + it "knife deps reports the role, along with an error" do + knife("deps --remote /roles/starring.json").should_fail( + exit_code: 2, + stdout: "/roles/minor.json\n/roles/starring.json\n", + stderr: "ERROR: /roles/minor.json: No such file or directory\n" + ) + end + end + end + context "invalid objects" do + when_the_chef_server "is empty" do + it "knife deps / reports an error" do + knife("deps --remote /").should_succeed("/\n") + end + it "knife deps /roles reports an error" do + knife("deps --remote /roles").should_succeed("/roles\n") + end + end + when_the_chef_server "has a data bag" do + before { data_bag "bag", { "item" => {} } } + it "knife deps /data_bags/bag shows no dependencies" do + knife("deps --remote /data_bags/bag").should_succeed("/data_bags/bag\n") + end + end + when_the_chef_server "has a cookbook" do + before do + cookbook "blah", "1.0.0", { "metadata.rb" => 'name "blah"' } + end + it "knife deps on a cookbook file shows no dependencies" do + knife("deps --remote /cookbooks/blah/metadata.rb").should_succeed( + "/cookbooks/blah/metadata.rb\n" + ) + end + end + end + end + + it "knife deps --no-recurse reports an error" do + knife("deps --no-recurse /").should_fail("ERROR: --no-recurse requires --tree\n") + end +end diff --git a/knife/spec/integration/diff_spec.rb b/knife/spec/integration/diff_spec.rb new file mode 100644 index 0000000000..c69573735a --- /dev/null +++ b/knife/spec/integration/diff_spec.rb @@ -0,0 +1,605 @@ +# +# 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/diff" + +describe "knife diff", :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 diff reports everything as deleted" do + 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 + 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 diff reports no differences" do + knife("diff /").should_succeed "" + end + + it "knife diff /environments/nonexistent.json reports an error" do + knife("diff /environments/nonexistent.json").should_fail "ERROR: /environments/nonexistent.json: No such file or directory on remote or local\n" + end + + it "knife diff /environments/*.txt reports an error" do + knife("diff /environments/*.txt").should_fail "ERROR: /environments/*.txt: No such file or directory on remote or local\n" + end + + context "except the role file" do + before do + file "roles/x.json", <<~EOM + { + "foo": "bar" + } + EOM + end + + it "knife diff reports the role as different" do + knife("diff --name-status /").should_succeed <<~EOM + M\t/roles/x.json + EOM + 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 diff reports the new files as added" do + knife("diff --name-status /").should_succeed <<~EOM + A\t/clients/y.json + A\t/cookbooks/x/blah.rb + A\t/cookbooks/y + A\t/data_bags/x/z.json + A\t/data_bags/y + A\t/environments/y.json + A\t/nodes/y.json + A\t/roles/y.json + A\t/users/y.json + EOM + end + + context "when cwd is the data_bags directory" do + before { cwd "data_bags" } + it "knife diff reports different data bags" do + knife("diff --name-status").should_succeed <<~EOM + A\tx/z.json + A\ty + EOM + end + it "knife diff * reports different data bags" do + knife("diff --name-status *").should_succeed <<~EOM + A\tx/z.json + A\ty + EOM + end + end + end + end + + when_the_repository "is empty" do + it "knife diff reports everything as deleted" do + 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 + 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", "" + 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" => "" } + end + + it "knife diff /cookbooks/x shows differences" do + knife("diff --name-status /cookbooks/x").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 + + it "knife diff --diff-filter=MAT does not show deleted files" do + knife("diff --diff-filter=MAT --name-status /cookbooks/x").should_succeed <<~EOM + M\t/cookbooks/x/metadata.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" => "" } + end + it "knife diff /cookbooks/x shows no differences" do + knife("diff --name-status /cookbooks/x").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" => "" } + end + + it "knife diff /cookbooks/x shows the differences" do + knife("diff --name-status /cookbooks/x").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" => "" } + end + + it "knife diff /cookbooks/x shows the differences" do + knife("diff --name-status /cookbooks/x").should_succeed <<~EOM + M\t/cookbooks/x/metadata.rb + D\t/cookbooks/x/onlyin0.9.9.rb + A\t/cookbooks/x/onlyin1.0.0.rb + EOM + end + end + end + + context "json diff tests" do + when_the_repository "has an empty environment file" do + before do + file "environments/x.json", {} + end + + when_the_chef_server "has an empty environment" do + before { environment "x", {} } + it "knife diff returns no differences" do + knife("diff /environments/x.json").should_succeed "" + end + end + when_the_chef_server "has an environment with a different value" do + before { environment "x", { "description" => "hi" } } + it "knife diff reports the difference" do + knife("diff /environments/x.json").should_succeed(/ + { +- "name": "x", +- "description": "hi" +\+ "name": "x" + } +/) + end + end + end + + when_the_repository "has an environment file with a value in it" do + before do + file "environments/x.json", { "description" => "hi" } + end + + when_the_chef_server "has an environment with the same value" do + before do + environment "x", { "description" => "hi" } + end + it "knife diff returns no differences" do + knife("diff /environments/x.json").should_succeed "" + end + end + when_the_chef_server "has an environment with no value" do + before do + environment "x", {} + end + + it "knife diff reports the difference" do + knife("diff /environments/x.json").should_succeed(/ + { +- "name": "x" +\+ "name": "x", +\+ "description": "hi" + } +/) + end + end + when_the_chef_server "has an environment with a different value" do + before do + environment "x", { "description" => "lo" } + end + it "knife diff reports the difference" do + knife("diff /environments/x.json").should_succeed(/ + { + "name": "x", +- "description": "lo" +\+ "description": "hi" + } +/) + end + end + end + end + + when_the_chef_server "has an environment" do + before { environment "x", {} } + when_the_repository "has an environment with bad JSON" do + before { file "environments/x.json", "{" } + it "knife diff reports an error and does a textual diff" do + error_text = "WARN: Parse error reading #{path_to("environments/x.json")} as JSON: parse error: premature EOF" + error_match = Regexp.new(Regexp.escape(error_text)) + knife("diff /environments/x.json").should_succeed(/- "name": "x"/, stderr: error_match) + 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 diff reports everything as deleted" do + 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 + 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 diff reports no differences" do + knife("diff /").should_succeed "" + end + + it "knife diff /environments/nonexistent.json reports an error" do + knife("diff /environments/nonexistent.json").should_fail "ERROR: /environments/nonexistent.json: No such file or directory on remote or local\n" + end + + it "knife diff /environments/*.txt reports an error" do + knife("diff /environments/*.txt").should_fail "ERROR: /environments/*.txt: No such file or directory on remote or local\n" + end + + context "except the role file" do + before do + file "roles/x.json", <<~EOM + { + "foo": "bar" + } + EOM + end + + it "knife diff reports the role as different" do + knife("diff --name-status /").should_succeed <<~EOM + M\t/roles/x.json + EOM + end + end + + context "as well as one extra copy of each thing" do + before do + file "clients/y.json", {} + 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", {} + end + + it "knife diff reports the new files as added" do + knife("diff --name-status /").should_succeed <<~EOM + A\t/clients/y.json + A\t/cookbooks/x-1.0.0/blah.rb + A\t/cookbooks/x-2.0.0 + A\t/cookbooks/y-1.0.0 + A\t/data_bags/x/z.json + A\t/data_bags/y + A\t/environments/y.json + A\t/nodes/y.json + A\t/roles/y.json + A\t/users/y.json + EOM + end + + context "when cwd is the data_bags directory" do + before { cwd "data_bags" } + it "knife diff reports different data bags" do + knife("diff --name-status").should_succeed <<~EOM + A\tx/z.json + A\ty + EOM + end + it "knife diff * reports different data bags" do + knife("diff --name-status *").should_succeed <<~EOM + A\tx/z.json + A\ty + EOM + end + end + end + end + + when_the_repository "is empty" do + it "knife diff reports everything as deleted" do + 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 + 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", "" + 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" => "" } + end + + it "knife diff /cookbooks shows differences" do + knife("diff --name-status /cookbooks").should_succeed <<~EOM + D\t/cookbooks/x-1.0.1 + EOM + end + + it "knife diff --diff-filter=MAT does not show deleted files" do + knife("diff --diff-filter=MAT --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" => "" } + end + it "knife diff /cookbooks shows the differences" do + knife("diff --name-status /cookbooks").should_succeed "D\t/cookbooks/x-0.9.9\n" + 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" => "" } + end + + it "knife diff /cookbooks shows the differences" do + knife("diff --name-status /cookbooks").should_succeed <<~EOM + D\t/cookbooks/x-1.0.1 + A\t/cookbooks/x-1.0.0 + 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" => "" } + end + + it "knife diff /cookbooks shows the differences" do + knife("diff --name-status /cookbooks").should_succeed <<~EOM + D\t/cookbooks/x-0.9.9 + A\t/cookbooks/x-1.0.0 + EOM + end + end + end + + context "json diff tests" do + when_the_repository "has an empty environment file" do + before { file "environments/x.json", {} } + when_the_chef_server "has an empty environment" do + before { environment "x", {} } + it "knife diff returns no differences" do + knife("diff /environments/x.json").should_succeed "" + end + end + when_the_chef_server "has an environment with a different value" do + before { environment "x", { "description" => "hi" } } + it "knife diff reports the difference" do + knife("diff /environments/x.json").should_succeed(/ + { +- "name": "x", +- "description": "hi" +\+ "name": "x" + } +/) + end + end + end + + when_the_repository "has an environment file with a value in it" do + before do + file "environments/x.json", { "description" => "hi" } + end + + when_the_chef_server "has an environment with the same value" do + before do + environment "x", { "description" => "hi" } + end + it "knife diff returns no differences" do + knife("diff /environments/x.json").should_succeed "" + end + end + when_the_chef_server "has an environment with no value" do + before { environment "x", {} } + it "knife diff reports the difference" do + knife("diff /environments/x.json").should_succeed(/ + { +- "name": "x" +\+ "name": "x", +\+ "description": "hi" + } +/) + end + end + when_the_chef_server "has an environment with a different value" do + before do + environment "x", { "description" => "lo" } + end + it "knife diff reports the difference" do + knife("diff /environments/x.json").should_succeed(/ + { + "name": "x", +- "description": "lo" +\+ "description": "hi" + } +/) + end + end + end + end + + when_the_chef_server "has an environment" do + before { environment "x", {} } + when_the_repository "has an environment with bad JSON" do + before { file "environments/x.json", "{" } + it "knife diff reports an error and does a textual diff" do + error_text = "WARN: Parse error reading #{path_to("environments/x.json")} as JSON: parse error: premature EOF" + error_match = Regexp.new(Regexp.escape(error_text)) + knife("diff /environments/x.json").should_succeed(/- "name": "x"/, stderr: error_match) + end + end + end + end # without versioned cookbooks +end diff --git a/knife/spec/integration/download_spec.rb b/knife/spec/integration/download_spec.rb new file mode 100644 index 0000000000..29200d66f2 --- /dev/null +++ b/knife/spec/integration/download_spec.rb @@ -0,0 +1,1336 @@ +# +# 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/download" +require "chef/knife/diff" + +describe "knife download", :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 download downloads everything" do + knife("download /").should_succeed <<~EOM + Created /clients/chef-validator.json + Created /clients/chef-webui.json + Created /clients/x.json + Created /cookbooks/x + Created /cookbooks/x/metadata.rb + Created /data_bags/x + Created /data_bags/x/y.json + Created /environments/_default.json + Created /environments/x.json + Created /nodes/x.json + Created /roles/x.json + Created /users/admin.json + Created /users/x.json + EOM + knife("diff --name-status /").should_succeed "" + 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 download makes no changes" do + knife("download /").should_succeed "" + knife("diff --name-status /").should_succeed "" + end + + it "knife download --purge makes no changes" do + knife("download --purge /").should_succeed "" + knife("diff --name-status /").should_succeed "" + end + + context "except the role file" do + before do + file "roles/x.json", <<~EOM + { + "chef_type": "role", + "default_attributes": { + }, + "description": "blarghle", + "env_run_lists": { + }, + "json_class": "Chef::Role", + "name": "x", + "override_attributes": { + }, + "run_list": [ + + ] + } + EOM + end + + it "knife download changes the role" do + knife("download /").should_succeed "Updated /roles/x.json\n" + knife("diff --name-status /").should_succeed "" + end + + it "knife download --no-diff does not change the role" do + knife("download --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 download / does not change anything" do + knife("download /").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/blah.rb", "" + file "cookbooks/y/metadata.rb", cb_metadata("x", "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 download does nothing" do + knife("download /").should_succeed "" + knife("diff --name-status /").should_succeed <<~EOM + A\t/clients/y.json + A\t/cookbooks/x/blah.rb + A\t/cookbooks/y + A\t/data_bags/x/z.json + A\t/data_bags/y + A\t/environments/y.json + A\t/nodes/y.json + A\t/roles/y.json + A\t/users/y.json + EOM + end + + it "knife download --purge deletes the extra files" do + knife("download --purge /").should_succeed <<~EOM + Deleted extra entry /clients/y.json (purge is on) + Deleted extra entry /cookbooks/x/blah.rb (purge is on) + Deleted extra entry /cookbooks/y (purge is on) + Deleted extra entry /data_bags/x/z.json (purge is on) + Deleted extra entry /data_bags/y (purge is on) + Deleted extra entry /environments/y.json (purge is on) + Deleted extra entry /nodes/y.json (purge is on) + Deleted extra entry /roles/y.json (purge is on) + Deleted extra entry /users/y.json (purge is on) + EOM + knife("diff --name-status /").should_succeed "" + end + end + end + + when_the_repository "is empty" do + it "knife download creates the extra files" do + knife("download /").should_succeed <<~EOM + Created /clients + Created /clients/chef-validator.json + Created /clients/chef-webui.json + Created /clients/x.json + Created /cookbooks + Created /cookbooks/x + Created /cookbooks/x/metadata.rb + Created /data_bags + Created /data_bags/x + Created /data_bags/x/y.json + Created /environments + Created /environments/_default.json + Created /environments/x.json + Created /nodes + Created /nodes/x.json + Created /roles + Created /roles/x.json + Created /users + Created /users/admin.json + Created /users/x.json + EOM + knife("diff --name-status /").should_succeed "" + end + + it "knife download --no-diff creates the extra files" do + knife("download --no-diff /").should_succeed <<~EOM + Created /clients + Created /clients/chef-validator.json + Created /clients/chef-webui.json + Created /clients/x.json + Created /cookbooks + Created /cookbooks/x + Created /cookbooks/x/metadata.rb + Created /data_bags + Created /data_bags/x + Created /data_bags/x/y.json + Created /environments + Created /environments/_default.json + Created /environments/x.json + Created /nodes + Created /nodes/x.json + Created /roles + Created /roles/x.json + Created /users + Created /users/admin.json + Created /users/x.json + EOM + knife("diff --name-status /").should_succeed "" + end + + context "when current directory is top level" do + before do + cwd "." + end + + it "knife download with no parameters reports an error" do + knife("download").should_fail "FATAL: You must specify at least one argument. If you want to download everything in this directory, run \"knife download .\"\n", stdout: /USAGE/ + end + end + end + end + + # Test download of an item when the other end doesn't even have the container + when_the_repository "is empty" do + when_the_chef_server "has two data bag items" do + before do + data_bag "x", { "y" => {}, "z" => {} } + end + + it "knife download of one data bag item itself succeeds" do + knife("download /data_bags/x/y.json").should_succeed <<~EOM + Created /data_bags + Created /data_bags/x + Created /data_bags/x/y.json + EOM + knife("diff --name-status /data_bags").should_succeed <<~EOM + D\t/data_bags/x/z.json + EOM + end + + it "knife download /data_bags/x /data_bags/x/y.json downloads x once" do + knife("download /data_bags/x /data_bags/x/y.json").should_succeed <<~EOM + Created /data_bags + Created /data_bags/x + Created /data_bags/x/y.json + Created /data_bags/x/z.json + EOM + end + end + end + + when_the_repository "has three data bag items" do + before do + file "data_bags/x/deleted.json", <<~EOM + { + "id": "deleted" + } + EOM + file "data_bags/x/modified.json", <<~EOM + { + "id": "modified" + } + EOM + file "data_bags/x/unmodified.json", <<~EOM + { + "id": "unmodified" + } + EOM + end + + when_the_chef_server "has a modified, unmodified, added and deleted data bag item" do + before do + data_bag "x", { + "added" => {}, + "modified" => { "foo" => "bar" }, + "unmodified" => {}, + } + end + + it "knife download of the modified file succeeds" do + knife("download /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/added.json + A\t/data_bags/x/deleted.json + EOM + end + it "knife download of the unmodified file does nothing" do + knife("download /data_bags/x/unmodified.json").should_succeed "" + knife("diff --name-status /data_bags").should_succeed <<~EOM + D\t/data_bags/x/added.json + M\t/data_bags/x/modified.json + A\t/data_bags/x/deleted.json + EOM + end + it "knife download of the added file succeeds" do + knife("download /data_bags/x/added.json").should_succeed <<~EOM + Created /data_bags/x/added.json + EOM + knife("diff --name-status /data_bags").should_succeed <<~EOM + M\t/data_bags/x/modified.json + A\t/data_bags/x/deleted.json + EOM + end + it "knife download of the deleted file does nothing" do + knife("download /data_bags/x/deleted.json").should_succeed "" + knife("diff --name-status /data_bags").should_succeed <<~EOM + D\t/data_bags/x/added.json + M\t/data_bags/x/modified.json + A\t/data_bags/x/deleted.json + EOM + end + it "knife download --purge of the deleted file deletes it" do + knife("download --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 + D\t/data_bags/x/added.json + M\t/data_bags/x/modified.json + EOM + end + it "knife download of the entire data bag downloads everything" do + knife("download /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 + A\t/data_bags/x/deleted.json + EOM + end + it "knife download --purge of the entire data bag downloads everything" do + knife("download --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 download fails" do + knife("download").should_fail "FATAL: You must specify at least one argument. If you want to download everything in this directory, run \"knife download .\"\n", stdout: /USAGE/ + end + it "knife download --purge . downloads everything" do + knife("download --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 download --purge * downloads everything" do + knife("download --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 + + when_the_repository "has a cookbook" do + before do + file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0") + file "cookbooks/x/z.rb", "" + end + + when_the_chef_server "has a modified, added and deleted file for the cookbook" do + before do + cookbook "x", "1.0.0", { "metadata.rb" => cb_metadata("x", "1.0.0", "#extra content"), "y.rb" => "hi" } + end + + it "knife download of a modified file succeeds" do + knife("download /cookbooks/x/metadata.rb").should_succeed "Updated /cookbooks/x/metadata.rb\n" + knife("diff --name-status /cookbooks").should_succeed <<~EOM + D\t/cookbooks/x/y.rb + A\t/cookbooks/x/z.rb + EOM + end + it "knife download of a deleted file does nothing" do + knife("download /cookbooks/x/z.rb").should_succeed "" + knife("diff --name-status /cookbooks").should_succeed <<~EOM + M\t/cookbooks/x/metadata.rb + D\t/cookbooks/x/y.rb + A\t/cookbooks/x/z.rb + EOM + end + it "knife download --purge of a deleted file succeeds" do + knife("download --purge /cookbooks/x/z.rb").should_succeed "Deleted extra entry /cookbooks/x/z.rb (purge is on)\n" + knife("diff --name-status /cookbooks").should_succeed <<~EOM + M\t/cookbooks/x/metadata.rb + D\t/cookbooks/x/y.rb + EOM + end + it "knife download of an added file succeeds" do + knife("download /cookbooks/x/y.rb").should_succeed "Created /cookbooks/x/y.rb\n" + knife("diff --name-status /cookbooks").should_succeed <<~EOM + M\t/cookbooks/x/metadata.rb + A\t/cookbooks/x/z.rb + EOM + end + it "knife download of the cookbook itself succeeds" do + knife("download /cookbooks/x").should_succeed <<~EOM + Updated /cookbooks/x/metadata.rb + Created /cookbooks/x/y.rb + EOM + knife("diff --name-status /cookbooks").should_succeed <<~EOM + A\t/cookbooks/x/z.rb + EOM + end + it "knife download --purge of the cookbook itself succeeds" do + knife("download --purge /cookbooks/x").should_succeed <<~EOM + Updated /cookbooks/x/metadata.rb + Created /cookbooks/x/y.rb + Deleted extra entry /cookbooks/x/z.rb (purge is on) + EOM + knife("diff --name-status /cookbooks").should_succeed "" + 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 download /cookbooks/x downloads the latest version" do + knife("download --purge /cookbooks/x").should_succeed <<~EOM + Updated /cookbooks/x/metadata.rb + Created /cookbooks/x/onlyin1.0.1.rb + Deleted extra entry /cookbooks/x/onlyin1.0.0.rb (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 download /cookbooks/x downloads the updated file" do + knife("download --purge /cookbooks/x").should_succeed <<~EOM + Updated /cookbooks/x/onlyin1.0.0.rb + 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 download /cookbooks/x downloads the latest version" do + knife("download --purge /cookbooks/x").should_succeed <<~EOM + Updated /cookbooks/x/metadata.rb + Created /cookbooks/x/onlyin1.0.1.rb + Deleted extra entry /cookbooks/x/onlyin1.0.0.rb (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 download /cookbooks/x downloads the old version" do + knife("download --purge /cookbooks/x").should_succeed <<~EOM + Updated /cookbooks/x/metadata.rb + Created /cookbooks/x/onlyin0.9.9.rb + Deleted extra entry /cookbooks/x/onlyin1.0.0.rb (purge is on) + EOM + knife("diff --name-status /cookbooks").should_succeed "" + end + end + end + + when_the_chef_server "has a role" do + before do + role "x", {} + end + when_the_repository "has the role in ruby" do + before do + file "roles/x.rb", <<~EOM + name "x" + description "x" + EOM + end + + it "knife download refuses to change the role" do + knife("download /roles/x.json").should_succeed "", stderr: "WARNING: /roles/x.rb cannot be updated (can't safely update ruby files).\n" + knife("diff --name-status /roles/x.json").should_succeed "M\t/roles/x.rb\n" + 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 download succeeds" do + warning = <<~EOH + WARN: Parse error reading #{path_to("environments/x.json")} as JSON: parse error: premature EOF + { + (right here) ------^ + + EOH + knife("download /environments/x.json").should_succeed "Updated /environments/x.json\n", stderr: warning + knife("diff --name-status /environments/x.json").should_succeed "" + 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 download succeeds" do + knife("download /environments/x.json").should_succeed "Updated /environments/x.json\n" + knife("diff --name-status /environments/x.json").should_succeed "" + 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 download succeeds" do + knife("download /environments/x.json").should_succeed "Updated /environments/x.json\n" + knife("diff --name-status /environments/x.json").should_succeed "" + 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 download downloads everything" do + knife("download /").should_succeed <<~EOM + Created /clients/chef-validator.json + Created /clients/chef-webui.json + Created /clients/x.json + Created /cookbooks/x-1.0.0 + Created /cookbooks/x-1.0.0/metadata.rb + Created /data_bags/x + Created /data_bags/x/y.json + Created /environments/_default.json + Created /environments/x.json + Created /nodes/x.json + Created /roles/x.json + Created /users/admin.json + Created /users/x.json + EOM + knife("diff --name-status /").should_succeed "" + 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 download makes no changes" do + knife("download /").should_succeed "" + knife("diff --name-status /").should_succeed "" + end + + it "knife download --purge makes no changes" do + knife("download --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 download changes the role" do + knife("download /").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 download / does not change anything" do + knife("download /").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", 'version "2.0.0"' + file "cookbooks/y-1.0.0/metadata.rb", 'version "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 download does nothing" do + knife("download /").should_succeed "" + knife("diff --name-status /").should_succeed <<~EOM + A\t/clients/y.json + A\t/cookbooks/x-1.0.0/blah.rb + A\t/cookbooks/x-2.0.0 + A\t/cookbooks/y-1.0.0 + A\t/data_bags/x/z.json + A\t/data_bags/y + A\t/environments/y.json + A\t/nodes/y.json + A\t/roles/y.json + A\t/users/y.json + EOM + end + + it "knife download --purge deletes the extra files" do + knife("download --purge /").should_succeed <<~EOM + Deleted extra entry /clients/y.json (purge is on) + Deleted extra entry /cookbooks/x-1.0.0/blah.rb (purge is on) + Deleted extra entry /cookbooks/x-2.0.0 (purge is on) + Deleted extra entry /cookbooks/y-1.0.0 (purge is on) + Deleted extra entry /data_bags/x/z.json (purge is on) + Deleted extra entry /data_bags/y (purge is on) + Deleted extra entry /environments/y.json (purge is on) + Deleted extra entry /nodes/y.json (purge is on) + Deleted extra entry /roles/y.json (purge is on) + Deleted extra entry /users/y.json (purge is on) + EOM + knife("diff --name-status /").should_succeed "" + end + end + end + + when_the_repository "is empty" do + it "knife download creates the extra files" do + knife("download /").should_succeed <<~EOM + Created /clients + Created /clients/chef-validator.json + Created /clients/chef-webui.json + Created /clients/x.json + Created /cookbooks + Created /cookbooks/x-1.0.0 + Created /cookbooks/x-1.0.0/metadata.rb + Created /data_bags + Created /data_bags/x + Created /data_bags/x/y.json + Created /environments + Created /environments/_default.json + Created /environments/x.json + Created /nodes + Created /nodes/x.json + Created /roles + Created /roles/x.json + Created /users + Created /users/admin.json + Created /users/x.json + EOM + knife("diff --name-status /").should_succeed "" + end + + context "when current directory is top level" do + before do + cwd "." + end + it "knife download with no parameters reports an error" do + knife("download").should_fail "FATAL: You must specify at least one argument. If you want to download everything in this directory, run \"knife download .\"\n", stdout: /USAGE/ + end + end + end + end + + # Test download of an item when the other end doesn't even have the container + when_the_repository "is empty" do + when_the_chef_server "has two data bag items" do + before do + data_bag "x", { "y" => {}, "z" => {} } + end + + it "knife download of one data bag item itself succeeds" do + knife("download /data_bags/x/y.json").should_succeed <<~EOM + Created /data_bags + Created /data_bags/x + Created /data_bags/x/y.json + EOM + knife("diff --name-status /data_bags").should_succeed <<~EOM + D\t/data_bags/x/z.json + EOM + end + end + end + + when_the_repository "has three data bag items" do + before do + file "data_bags/x/deleted.json", <<~EOM + { + "id": "deleted" + } + EOM + file "data_bags/x/modified.json", <<~EOM + { + "id": "modified" + } + EOM + file "data_bags/x/unmodified.json", <<~EOM + { + "id": "unmodified" + } + EOM + end + + when_the_chef_server "has a modified, unmodified, added and deleted data bag item" do + before do + data_bag "x", { + "added" => {}, + "modified" => { "foo" => "bar" }, + "unmodified" => {}, + } + end + + it "knife download of the modified file succeeds" do + knife("download /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/added.json + A\t/data_bags/x/deleted.json + EOM + end + it "knife download of the unmodified file does nothing" do + knife("download /data_bags/x/unmodified.json").should_succeed "" + knife("diff --name-status /data_bags").should_succeed <<~EOM + D\t/data_bags/x/added.json + M\t/data_bags/x/modified.json + A\t/data_bags/x/deleted.json + EOM + end + it "knife download of the added file succeeds" do + knife("download /data_bags/x/added.json").should_succeed <<~EOM + Created /data_bags/x/added.json + EOM + knife("diff --name-status /data_bags").should_succeed <<~EOM + M\t/data_bags/x/modified.json + A\t/data_bags/x/deleted.json + EOM + end + it "knife download of the deleted file does nothing" do + knife("download /data_bags/x/deleted.json").should_succeed "" + knife("diff --name-status /data_bags").should_succeed <<~EOM + D\t/data_bags/x/added.json + M\t/data_bags/x/modified.json + A\t/data_bags/x/deleted.json + EOM + end + it "knife download --purge of the deleted file deletes it" do + knife("download --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 + D\t/data_bags/x/added.json + M\t/data_bags/x/modified.json + EOM + end + it "knife download of the entire data bag downloads everything" do + knife("download /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 + A\t/data_bags/x/deleted.json + EOM + end + it "knife download --purge of the entire data bag downloads everything" do + knife("download --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 download fails" do + knife("download").should_fail "FATAL: You must specify at least one argument. If you want to download everything in this directory, run \"knife download .\"\n", stdout: /USAGE/ + end + it "knife download --purge . downloads everything" do + knife("download --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 download --purge * downloads everything" do + knife("download --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 + + when_the_repository "has a cookbook" do + before do + file "cookbooks/x-1.0.0/metadata.rb", 'name "x"; version "1.0.0"#unmodified' + file "cookbooks/x-1.0.0/z.rb", "" + end + + when_the_chef_server "has a modified, added and deleted file for the cookbook" do + before do + cookbook "x", "1.0.0", { "y.rb" => "hi" } + end + + it "knife download of a modified file succeeds" do + knife("download /cookbooks/x-1.0.0/metadata.rb").should_succeed "Updated /cookbooks/x-1.0.0/metadata.rb\n" + knife("diff --name-status /cookbooks").should_succeed <<~EOM + D\t/cookbooks/x-1.0.0/y.rb + A\t/cookbooks/x-1.0.0/z.rb + EOM + end + it "knife download of a deleted file does nothing" do + knife("download /cookbooks/x-1.0.0/z.rb").should_succeed "" + knife("diff --name-status /cookbooks").should_succeed <<~EOM + M\t/cookbooks/x-1.0.0/metadata.rb + D\t/cookbooks/x-1.0.0/y.rb + A\t/cookbooks/x-1.0.0/z.rb + EOM + end + it "knife download --purge of a deleted file succeeds" do + knife("download --purge /cookbooks/x-1.0.0/z.rb").should_succeed "Deleted extra entry /cookbooks/x-1.0.0/z.rb (purge is on)\n" + knife("diff --name-status /cookbooks").should_succeed <<~EOM + M\t/cookbooks/x-1.0.0/metadata.rb + D\t/cookbooks/x-1.0.0/y.rb + EOM + end + it "knife download of an added file succeeds" do + knife("download /cookbooks/x-1.0.0/y.rb").should_succeed "Created /cookbooks/x-1.0.0/y.rb\n" + knife("diff --name-status /cookbooks").should_succeed <<~EOM + M\t/cookbooks/x-1.0.0/metadata.rb + A\t/cookbooks/x-1.0.0/z.rb + EOM + end + it "knife download of the cookbook itself succeeds" do + knife("download /cookbooks/x-1.0.0").should_succeed <<~EOM + Updated /cookbooks/x-1.0.0/metadata.rb + Created /cookbooks/x-1.0.0/y.rb + EOM + knife("diff --name-status /cookbooks").should_succeed <<~EOM + A\t/cookbooks/x-1.0.0/z.rb + EOM + end + it "knife download --purge of the cookbook itself succeeds" do + knife("download --purge /cookbooks/x-1.0.0").should_succeed <<~EOM + Updated /cookbooks/x-1.0.0/metadata.rb + Created /cookbooks/x-1.0.0/y.rb + Deleted extra entry /cookbooks/x-1.0.0/z.rb (purge is on) + 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 download /cookbooks/x downloads the latest version" do + knife("download --purge /cookbooks").should_succeed <<~EOM + Updated /cookbooks/x-1.0.0/onlyin1.0.0.rb + Created /cookbooks/x-1.0.1 + Created /cookbooks/x-1.0.1/metadata.rb + Created /cookbooks/x-1.0.1/onlyin1.0.1.rb + 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 download /cookbooks downloads the updated file" do + knife("download --purge /cookbooks").should_succeed <<~EOM + Created /cookbooks/x-0.9.9 + Created /cookbooks/x-0.9.9/metadata.rb + Created /cookbooks/x-0.9.9/onlyin0.9.9.rb + Updated /cookbooks/x-1.0.0/onlyin1.0.0.rb + 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 download /cookbooks/x downloads the latest version" do + knife("download --purge /cookbooks").should_succeed <<~EOM + Created /cookbooks/x-1.0.1 + Created /cookbooks/x-1.0.1/metadata.rb + Created /cookbooks/x-1.0.1/onlyin1.0.1.rb + Deleted extra entry /cookbooks/x-1.0.0 (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 download --purge /cookbooks downloads the old version and deletes the new version" do + knife("download --purge /cookbooks").should_succeed <<~EOM + Created /cookbooks/x-0.9.9 + Created /cookbooks/x-0.9.9/metadata.rb + Created /cookbooks/x-0.9.9/onlyin0.9.9.rb + Deleted extra entry /cookbooks/x-1.0.0 (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 download succeeds" do + knife("download /environments/x.json").should_succeed "Updated /environments/x.json\n" + knife("diff --name-status /environments/x.json").should_succeed "" + 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 download succeeds" do + knife("download /environments/x.json").should_succeed "Updated /environments/x.json\n" + knife("diff --name-status /environments/x.json").should_succeed "" + end + end + end + end # with versioned cookbooks + + when_the_chef_server "has a cookbook" do + before do + cookbook "x", "1.0.0" + end + + when_the_repository "is empty" do + it "knife download /cookbooks/x signs all requests" do + + # Check that BasicClient.request() always gets called with X-OPS-USERID + original_new = Chef::HTTP::BasicClient.method(:new) + expect(Chef::HTTP::BasicClient).to receive(:new) { |args| + new_result = original_new.call(*args) + original_request = new_result.method(:request) + expect(new_result).to receive(:request) { |method, url, body, headers, &response_handler| + expect(headers["X-OPS-USERID"]).not_to be_nil + original_request.call(method, url, body, headers, &response_handler) + }.at_least(:once) + new_result + }.at_least(:once) + + knife("download /cookbooks/x").should_succeed <<~EOM + Created /cookbooks + Created /cookbooks/x + Created /cookbooks/x/metadata.rb + EOM + end + end + end + + when_the_chef_server "is in Enterprise mode", osc_compat: false, single_org: false do + before do + user "foo", {} + user "bar", {} + user "foobar", {} + organization "foo", { "full_name" => "Something" } + end + + before :each do + Chef::Config.chef_server_url = URI.join(Chef::Config.chef_server_url, "/organizations/foo") + end + + when_the_repository "has all the default stuff" do + before do + knife("download /").should_succeed <<~EOM + Created /acls + Created /acls/clients + Created /acls/clients/foo-validator.json + Created /acls/containers + Created /acls/containers/clients.json + Created /acls/containers/containers.json + Created /acls/containers/cookbook_artifacts.json + Created /acls/containers/cookbooks.json + Created /acls/containers/data.json + Created /acls/containers/environments.json + Created /acls/containers/groups.json + Created /acls/containers/nodes.json + Created /acls/containers/policies.json + Created /acls/containers/policy_groups.json + Created /acls/containers/roles.json + Created /acls/containers/sandboxes.json + Created /acls/cookbook_artifacts + Created /acls/cookbooks + Created /acls/data_bags + Created /acls/environments + Created /acls/environments/_default.json + Created /acls/groups + Created /acls/groups/admins.json + Created /acls/groups/billing-admins.json + Created /acls/groups/clients.json + Created /acls/groups/users.json + Created /acls/nodes + Created /acls/policies + Created /acls/policy_groups + Created /acls/roles + Created /acls/organization.json + Created /clients + Created /clients/foo-validator.json + Created /containers + Created /containers/clients.json + Created /containers/containers.json + Created /containers/cookbook_artifacts.json + Created /containers/cookbooks.json + Created /containers/data.json + Created /containers/environments.json + Created /containers/groups.json + Created /containers/nodes.json + Created /containers/policies.json + Created /containers/policy_groups.json + Created /containers/roles.json + Created /containers/sandboxes.json + Created /cookbook_artifacts + Created /cookbooks + Created /data_bags + Created /environments + Created /environments/_default.json + Created /groups + Created /groups/admins.json + Created /groups/billing-admins.json + Created /groups/clients.json + Created /groups/users.json + Created /invitations.json + Created /members.json + Created /nodes + Created /org.json + Created /policies + Created /policy_groups + Created /roles + EOM + end + + context "and the server has one of each thing" do + before do + # acl_for %w(organizations foo groups blah) + client "x", {} + cookbook "x", "1.0.0" + 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 + + before do + knife("download /acls /groups/clients.json /groups/users.json").should_succeed <<~EOM + Created /acls/clients/x.json + Created /acls/containers/x.json + Created /acls/cookbook_artifacts/x.json + Created /acls/cookbooks/x.json + Created /acls/data_bags/x.json + Created /acls/environments/x.json + Created /acls/groups/x.json + Created /acls/nodes/x.json + Created /acls/policies/blah.json + Created /acls/policies/x.json + Created /acls/policy_groups/x.json + Created /acls/roles/x.json + Updated /groups/clients.json + Updated /groups/users.json + EOM + end + + it "knife download / downloads everything" do + knife("download /").should_succeed <<~EOM + Created /clients/x.json + Created /containers/x.json + Created /cookbook_artifacts/x-1x1 + Created /cookbook_artifacts/x-1x1/metadata.rb + Created /cookbooks/x + Created /cookbooks/x/metadata.rb + Created /data_bags/x + Created /data_bags/x/y.json + Created /environments/x.json + Created /groups/x.json + Updated /invitations.json + Updated /members.json + Created /nodes/x.json + Created /policies/blah-1.0.0.json + Created /policies/x-1.0.0.json + Created /policy_groups/x.json + Created /roles/x.json + EOM + knife("diff --name-status /").should_succeed "" + end + + context "and the repository has an identical copy of each thing" do + before do + # TODO We have to upload acls for an existing group due to a lack of + # dependency detection during upload. Fix that! + file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY } + file "containers/x.json", {} + file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0") + file "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 "nodes/x.json", { "normal" => { "tags" => [] } } + file "org.json", { "full_name" => "Something" } + file "policies/x-1.0.0.json", {} + file "policies/blah-1.0.0.json", {} + file "policy_groups/x.json", { "policies" => { "x" => { "revision_id" => "1.0.0" }, "blah" => { "revision_id" => "1.0.0" } } } + file "roles/x.json", {} + end + + it "knife download makes no changes" do + knife("download /").should_succeed "" + end + end + + context "and the repository has a slightly different copy of each thing" do + before do + # acl_for %w(organizations foo groups blah) + file "clients/x.json", { "validator" => true } + file "containers/x.json", {} + file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.1") + file "cookbook_artifacts/x-1x1/metadata.rb", cb_metadata("x", "1.0.1") + file "data_bags/x/y.json", { "a" => "b" } + file "environments/x.json", { "description" => "foo" } + file "groups/x.json", { "description" => "foo" } + file "groups/x.json", { "groups" => [ "admin" ] } + file "nodes/x.json", { "normal" => { "tags" => [] }, "run_list" => [ "blah" ] } + file "org.json", { "full_name" => "Something Else " } + file "policies/x-1.0.0.json", { "run_list" => [ "blah" ] } + file "policy_groups/x.json", { + "policies" => { + "x" => { "revision_id" => "1.0.1" }, + "y" => { "revision_id" => "1.0.0" }, + }, + } + file "roles/x.json", { "run_list" => [ "blah" ] } + end + + it "knife download updates everything" do + knife("download /").should_succeed <<~EOM + Updated /clients/x.json + Updated /cookbook_artifacts/x-1x1/metadata.rb + Updated /cookbooks/x/metadata.rb + Updated /data_bags/x/y.json + Updated /environments/x.json + Updated /groups/x.json + Updated /invitations.json + Updated /members.json + Updated /nodes/x.json + Updated /org.json + Created /policies/blah-1.0.0.json + Updated /policies/x-1.0.0.json + Updated /policy_groups/x.json + Updated /roles/x.json + EOM + knife("diff --name-status /").should_succeed "" + end + end + end + end + end +end diff --git a/knife/spec/integration/environment_compare_spec.rb b/knife/spec/integration/environment_compare_spec.rb new file mode 100644 index 0000000000..a8d207466a --- /dev/null +++ b/knife/spec/integration/environment_compare_spec.rb @@ -0,0 +1,75 @@ +# +# 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 "support/shared/context/config" + +describe "knife environment compare", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has some environments" do + before do + cookbook "blah", "1.0.1" + cookbook "blah", "1.1.1" + cookbook "krad", "1.1.1" + environment "x", { + "cookbook_versions" => { + "blah" => "= 1.0.0", + "krad" => ">= 1.0.0", + }, + } + environment "y", { + "cookbook_versions" => { + "blah" => "= 1.1.0", + "krad" => ">= 1.0.0", + }, + } + end + + # rubocop:disable Layout/TrailingWhitespace + it "displays the cookbooks for a single environment" do + knife("environment compare x").should_succeed <<~EOM + x + blah = 1.0.0 + krad >= 1.0.0 + + EOM + end + + it "compares the cookbooks for two environments" do + knife("environment compare x y").should_succeed <<~EOM + x y + blah = 1.0.0 = 1.1.0 + krad >= 1.0.0 >= 1.0.0 + + EOM + end + + it "compares the cookbooks for all environments" do + knife("environment compare --all").should_succeed <<~EOM + x y + blah = 1.0.0 = 1.1.0 + krad >= 1.0.0 >= 1.0.0 + + EOM + end + # rubocop:enable Layout/TrailingWhitespace + end +end diff --git a/knife/spec/integration/environment_create_spec.rb b/knife/spec/integration/environment_create_spec.rb new file mode 100644 index 0000000000..496828073d --- /dev/null +++ b/knife/spec/integration/environment_create_spec.rb @@ -0,0 +1,41 @@ +# +# 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 "support/shared/context/config" + +describe "knife environment create", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + let(:out) { "Created bah\n" } + + when_the_chef_server "is empty" do + it "creates a new environment" do + knife("environment create bah").should_succeed out + end + + it "refuses to add an existing environment" do + pending "Knife environment create must not blindly overwrite an existing environment" + knife("environment create bah").should_succeed out + expect { knife("environment create bah") }.to raise_error(Net::HTTPClientException) + end + + end +end diff --git a/knife/spec/integration/environment_delete_spec.rb b/knife/spec/integration/environment_delete_spec.rb new file mode 100644 index 0000000000..93427aaf2f --- /dev/null +++ b/knife/spec/integration/environment_delete_spec.rb @@ -0,0 +1,37 @@ +# +# 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 "support/shared/context/config" + +describe "knife environment delete", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has an environment" do + before do + environment "y", {} + end + + it "deletes an environment" do + knife("environment delete y", input: "y").should_succeed "Do you really want to delete y? (Y/N) Deleted y\n" + end + + end +end diff --git a/knife/spec/integration/environment_from_file_spec.rb b/knife/spec/integration/environment_from_file_spec.rb new file mode 100644 index 0000000000..e5ba056bb7 --- /dev/null +++ b/knife/spec/integration/environment_from_file_spec.rb @@ -0,0 +1,116 @@ +# +# 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 "support/shared/context/config" + +describe "knife environment from file", :workstation do + include IntegrationSupport + include KnifeSupport + + # include_context "default config options" + + let(:env_dir) { "#{@repository_dir}/environments" } + + when_the_chef_server "is empty" do + when_the_repository "has some environments" do + before do + + file "environments/cons.json", <<~EOM + { + "name": "cons", + "description": "An environment", + "cookbook_versions": { + + }, + "json_class": "Chef::Environment", + "chef_type": "environment", + "default_attributes": { + "hola": "Amigos!" + }, + "override_attributes": { + + } + } + EOM + + file "environments/car.json", <<~EOM + { + "name": "car", + "description": "An environment for list nodes", + "cookbook_versions": { + + }, + "json_class": "Chef::Environment", + "chef_type": "environment", + "default_attributes": { + "hola": "Amigos!" + }, + "override_attributes": { + + } + } + EOM + + file "environments/cdr.json", <<~EOM + { + "name": "cdr", + "description": "An environment for last nodes", + "cookbook_versions": { + + }, + "json_class": "Chef::Environment", + "chef_type": "environment", + "default_attributes": { + "hola": "Amigos!" + }, + "override_attributes": { + + } + } + EOM + + end + + it "uploads a single file" do + knife("environment from file #{env_dir}/cons.json").should_succeed stderr: <<~EOM + Updated Environment cons + EOM + end + + it "uploads many files" do + knife("environment from file #{env_dir}/cons.json #{env_dir}/car.json #{env_dir}/cdr.json").should_succeed stderr: <<~EOM + Updated Environment cons + Updated Environment car + Updated Environment cdr + EOM + end + + it "uploads all environments in the repository" do + cwd(".") + knife("environment from file --all") + knife("environment list").should_succeed <<~EOM + _default + car + cdr + cons + EOM + end + + end + end +end diff --git a/knife/spec/integration/environment_list_spec.rb b/knife/spec/integration/environment_list_spec.rb new file mode 100644 index 0000000000..f74b2b6360 --- /dev/null +++ b/knife/spec/integration/environment_list_spec.rb @@ -0,0 +1,42 @@ +# +# 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 "support/shared/context/config" + +describe "knife environment list", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has some environments" do + before do + environment "b", {} + environment "y", {} + end + + it "lists all the environments" do + knife("environment list").should_succeed <<~EOM + _default + b + y + EOM + end + + end +end diff --git a/knife/spec/integration/environment_show_spec.rb b/knife/spec/integration/environment_show_spec.rb new file mode 100644 index 0000000000..b961e85734 --- /dev/null +++ b/knife/spec/integration/environment_show_spec.rb @@ -0,0 +1,77 @@ +# +# 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 "support/shared/context/config" + +describe "knife environment show", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has some environments" do + before do + environment "b", { + "default_attributes" => { "foo" => "bar", "baz" => { "raz.my" => "mataz" } }, + } + end + + # rubocop:disable Layout/TrailingWhitespace + it "shows an environment" do + knife("environment show b").should_succeed <<~EOM + chef_type: environment + cookbook_versions: + default_attributes: + baz: + raz.my: mataz + foo: bar + description: + json_class: Chef::Environment + name: b + override_attributes: + EOM + end + # rubocop:enable Layout/TrailingWhitespace + + it "shows the requested attribute of an environment" do + knife("environment show b -a default_attributes").should_succeed <<~EOM + b: + default_attributes: + baz: + raz.my: mataz + foo: bar + EOM + end + + it "shows the requested nested attribute of an environment" do + knife("environment show b -a default_attributes.baz").should_succeed <<~EON + b: + default_attributes.baz: + raz.my: mataz + EON + end + + it "shows the requested attribute of an environment with custom field separator" do + knife("environment show b -S: -a default_attributes:baz").should_succeed <<~EOT + b: + default_attributes:baz: + raz.my: mataz + EOT + end + end +end diff --git a/knife/spec/integration/list_spec.rb b/knife/spec/integration/list_spec.rb new file mode 100644 index 0000000000..8228ba6056 --- /dev/null +++ b/knife/spec/integration/list_spec.rb @@ -0,0 +1,1060 @@ +# +# 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 "support/shared/context/config" +require "chef/knife/list" + +describe "knife list", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "is empty" do + it "knife list / returns all top level directories" do + knife("list /").should_succeed <<~EOM + /clients + /cookbooks + /data_bags + /environments + /nodes + /roles + /users + EOM + end + + it "knife list -R / returns everything" do + knife("list -R /").should_succeed <<~EOM + /: + clients + cookbooks + data_bags + environments + nodes + roles + users + + /clients: + chef-validator.json + chef-webui.json + + /cookbooks: + + /data_bags: + + /environments: + _default.json + + /nodes: + + /roles: + + /users: + admin.json + EOM + end + end + + when_the_chef_server "has plenty of stuff in it" do + before do + client "client1", {} + client "client2", {} + cookbook "cookbook1", "1.0.0" + cookbook "cookbook2", "1.0.1", { "recipes" => { "default.rb" => "" } } + data_bag "bag1", { "item1" => {}, "item2" => {} } + data_bag "bag2", { "item1" => {}, "item2" => {} } + environment "environment1", {} + environment "environment2", {} + node "node1", {} + node "node2", {} + policy "policy1", "1.2.3", {} + policy "policy2", "1.2.3", {} + policy "policy2", "1.3.5", {} + role "role1", {} + role "role2", {} + user "user1", {} + user "user2", {} + end + + it "knife list / returns all top level directories" do + knife("list /").should_succeed <<~EOM + /clients + /cookbooks + /data_bags + /environments + /nodes + /roles + /users + EOM + end + + it "knife list -R / returns everything" do + knife("list -R /").should_succeed <<~EOM + /: + clients + cookbooks + data_bags + environments + nodes + roles + users + + /clients: + chef-validator.json + chef-webui.json + client1.json + client2.json + + /cookbooks: + cookbook1 + cookbook2 + + /cookbooks/cookbook1: + metadata.rb + + /cookbooks/cookbook2: + metadata.rb + recipes + + /cookbooks/cookbook2/recipes: + default.rb + + /data_bags: + bag1 + bag2 + + /data_bags/bag1: + item1.json + item2.json + + /data_bags/bag2: + item1.json + item2.json + + /environments: + _default.json + environment1.json + environment2.json + + /nodes: + node1.json + node2.json + + /roles: + role1.json + role2.json + + /users: + admin.json + user1.json + user2.json + EOM + end + + it "knife list -R --flat / returns everything" do + knife("list -R --flat /").should_succeed <<~EOM + /clients + /clients/chef-validator.json + /clients/chef-webui.json + /clients/client1.json + /clients/client2.json + /cookbooks + /cookbooks/cookbook1 + /cookbooks/cookbook1/metadata.rb + /cookbooks/cookbook2 + /cookbooks/cookbook2/metadata.rb + /cookbooks/cookbook2/recipes + /cookbooks/cookbook2/recipes/default.rb + /data_bags + /data_bags/bag1 + /data_bags/bag1/item1.json + /data_bags/bag1/item2.json + /data_bags/bag2 + /data_bags/bag2/item1.json + /data_bags/bag2/item2.json + /environments + /environments/_default.json + /environments/environment1.json + /environments/environment2.json + /nodes + /nodes/node1.json + /nodes/node2.json + /roles + /roles/role1.json + /roles/role2.json + /users + /users/admin.json + /users/user1.json + /users/user2.json + EOM + end + + it "knife list -Rfp / returns everything" do + knife("list -Rfp /").should_succeed <<~EOM + /clients/ + /clients/chef-validator.json + /clients/chef-webui.json + /clients/client1.json + /clients/client2.json + /cookbooks/ + /cookbooks/cookbook1/ + /cookbooks/cookbook1/metadata.rb + /cookbooks/cookbook2/ + /cookbooks/cookbook2/metadata.rb + /cookbooks/cookbook2/recipes/ + /cookbooks/cookbook2/recipes/default.rb + /data_bags/ + /data_bags/bag1/ + /data_bags/bag1/item1.json + /data_bags/bag1/item2.json + /data_bags/bag2/ + /data_bags/bag2/item1.json + /data_bags/bag2/item2.json + /environments/ + /environments/_default.json + /environments/environment1.json + /environments/environment2.json + /nodes/ + /nodes/node1.json + /nodes/node2.json + /roles/ + /roles/role1.json + /roles/role2.json + /users/ + /users/admin.json + /users/user1.json + /users/user2.json + EOM + end + + it "knife list /cookbooks returns the list of cookbooks" do + knife("list /cookbooks").should_succeed <<~EOM + /cookbooks/cookbook1 + /cookbooks/cookbook2 + EOM + end + + it "knife list /cookbooks/*2/*/*.rb returns the one file" do + knife("list /cookbooks/*2/*/*.rb").should_succeed "/cookbooks/cookbook2/recipes/default.rb\n" + end + + it "knife list /**.rb returns all ruby files" do + knife("list /**.rb").should_succeed <<~EOM + /cookbooks/cookbook1/metadata.rb + /cookbooks/cookbook2/metadata.rb + /cookbooks/cookbook2/recipes/default.rb + EOM + end + + it "knife list /cookbooks/**.rb returns all ruby files" do + knife("list /cookbooks/**.rb").should_succeed <<~EOM + /cookbooks/cookbook1/metadata.rb + /cookbooks/cookbook2/metadata.rb + /cookbooks/cookbook2/recipes/default.rb + EOM + end + + it "knife list /**.json returns all json files" do + knife("list /**.json").should_succeed <<~EOM + /clients/chef-validator.json + /clients/chef-webui.json + /clients/client1.json + /clients/client2.json + /data_bags/bag1/item1.json + /data_bags/bag1/item2.json + /data_bags/bag2/item1.json + /data_bags/bag2/item2.json + /environments/_default.json + /environments/environment1.json + /environments/environment2.json + /nodes/node1.json + /nodes/node2.json + /roles/role1.json + /roles/role2.json + /users/admin.json + /users/user1.json + /users/user2.json + EOM + end + + it "knife list /data**.json returns all data bag json files" do + knife("list /data**.json").should_succeed <<~EOM + /data_bags/bag1/item1.json + /data_bags/bag1/item2.json + /data_bags/bag2/item1.json + /data_bags/bag2/item2.json + EOM + end + + it "knife list /environments/missing_file.json reports missing file" do + knife("list /environments/missing_file.json").should_fail "ERROR: /environments/missing_file.json: No such file or directory\n" + end + + context "missing file/directory exact match tests" do + it "knife list /blarghle reports missing directory" do + knife("list /blarghle").should_fail "ERROR: /blarghle: No such file or directory\n" + end + end + + context "symlink tests" do + when_the_repository "is empty" do + context "when cwd is at the top of the repository" do + before { cwd "." } + + it "knife list -Rfp returns everything" do + knife("list -Rfp").should_succeed <<~EOM + clients/ + clients/chef-validator.json + clients/chef-webui.json + clients/client1.json + clients/client2.json + cookbooks/ + cookbooks/cookbook1/ + cookbooks/cookbook1/metadata.rb + cookbooks/cookbook2/ + cookbooks/cookbook2/metadata.rb + cookbooks/cookbook2/recipes/ + cookbooks/cookbook2/recipes/default.rb + data_bags/ + data_bags/bag1/ + data_bags/bag1/item1.json + data_bags/bag1/item2.json + data_bags/bag2/ + data_bags/bag2/item1.json + data_bags/bag2/item2.json + environments/ + environments/_default.json + environments/environment1.json + environments/environment2.json + nodes/ + nodes/node1.json + nodes/node2.json + roles/ + roles/role1.json + roles/role2.json + users/ + users/admin.json + users/user1.json + users/user2.json + EOM + end + end + end + + when_the_repository "has a cookbooks directory" do + before { directory "cookbooks" } + context "when cwd is in cookbooks/" do + before { cwd "cookbooks" } + + it "knife list -Rfp / returns everything" do + knife("list -Rfp /").should_succeed <<~EOM + /clients/ + /clients/chef-validator.json + /clients/chef-webui.json + /clients/client1.json + /clients/client2.json + ./ + cookbook1/ + cookbook1/metadata.rb + cookbook2/ + cookbook2/metadata.rb + cookbook2/recipes/ + cookbook2/recipes/default.rb + /data_bags/ + /data_bags/bag1/ + /data_bags/bag1/item1.json + /data_bags/bag1/item2.json + /data_bags/bag2/ + /data_bags/bag2/item1.json + /data_bags/bag2/item2.json + /environments/ + /environments/_default.json + /environments/environment1.json + /environments/environment2.json + /nodes/ + /nodes/node1.json + /nodes/node2.json + /roles/ + /roles/role1.json + /roles/role2.json + /users/ + /users/admin.json + /users/user1.json + /users/user2.json + EOM + end + + it "knife list -Rfp .. returns everything" do + knife("list -Rfp ..").should_succeed <<~EOM + /clients/ + /clients/chef-validator.json + /clients/chef-webui.json + /clients/client1.json + /clients/client2.json + ./ + cookbook1/ + cookbook1/metadata.rb + cookbook2/ + cookbook2/metadata.rb + cookbook2/recipes/ + cookbook2/recipes/default.rb + /data_bags/ + /data_bags/bag1/ + /data_bags/bag1/item1.json + /data_bags/bag1/item2.json + /data_bags/bag2/ + /data_bags/bag2/item1.json + /data_bags/bag2/item2.json + /environments/ + /environments/_default.json + /environments/environment1.json + /environments/environment2.json + /nodes/ + /nodes/node1.json + /nodes/node2.json + /roles/ + /roles/role1.json + /roles/role2.json + /users/ + /users/admin.json + /users/user1.json + /users/user2.json + EOM + end + + it "knife list -Rfp returns cookbooks" do + knife("list -Rfp").should_succeed <<~EOM + cookbook1/ + cookbook1/metadata.rb + cookbook2/ + cookbook2/metadata.rb + cookbook2/recipes/ + cookbook2/recipes/default.rb + EOM + end + end + end + + when_the_repository "has a cookbooks/cookbook2 directory" do + before { directory "cookbooks/cookbook2" } + + context "when cwd is in cookbooks/cookbook2" do + before { cwd "cookbooks/cookbook2" } + + it "knife list -Rfp returns cookbooks" do + knife("list -Rfp").should_succeed <<~EOM + metadata.rb + recipes/ + recipes/default.rb + EOM + end + end + end + + when_the_repository "has a cookbooks directory and a symlinked cookbooks directory", skip: (ChefUtils.windows?) do + before do + directory "cookbooks" + symlink "symlinked", "cookbooks" + end + + context "when cwd is in cookbooks/" do + before { cwd "cookbooks" } + + it "knife list -Rfp returns cookbooks" do + knife("list -Rfp").should_succeed <<~EOM + cookbook1/ + cookbook1/metadata.rb + cookbook2/ + cookbook2/metadata.rb + cookbook2/recipes/ + cookbook2/recipes/default.rb + EOM + end + end + + context "when cwd is in symlinked/" do + before { cwd "symlinked" } + + it "knife list -Rfp returns cookbooks" do + knife("list -Rfp").should_succeed <<~EOM + cookbook1/ + cookbook1/metadata.rb + cookbook2/ + cookbook2/metadata.rb + cookbook2/recipes/ + cookbook2/recipes/default.rb + EOM + end + end + end + + when_the_repository "has a real_cookbooks directory and a cookbooks symlink to it", skip: (ChefUtils.windows?) do + before do + directory "real_cookbooks" + symlink "cookbooks", "real_cookbooks" + end + + context "when cwd is in real_cookbooks/" do + before { cwd "real_cookbooks" } + + it "knife list -Rfp returns cookbooks" do + knife("list -Rfp").should_succeed <<~EOM + cookbook1/ + cookbook1/metadata.rb + cookbook2/ + cookbook2/metadata.rb + cookbook2/recipes/ + cookbook2/recipes/default.rb + EOM + end + end + + context "when cwd is in cookbooks/" do + before { cwd "cookbooks" } + + it "knife list -Rfp returns cookbooks" do + knife("list -Rfp").should_succeed <<~EOM + cookbook1/ + cookbook1/metadata.rb + cookbook2/ + cookbook2/metadata.rb + cookbook2/recipes/ + cookbook2/recipes/default.rb + EOM + end + end + end + end + end + + context "--local" do + when_the_repository "is empty" do + it "knife list --local / returns nothing" do + knife("list --local /").should_succeed "" + end + + it "knife list /roles returns nothing" do + knife("list --local /roles").should_fail "ERROR: /roles: No such file or directory\n" + end + end + + when_the_repository "has a bunch of stuff" do + before do + file "clients/client1.json", {} + file "clients/client2.json", {} + + directory "cookbooks/cookbook1" do + file "metadata.rb", cb_metadata("cookbook1", "1.0.0") + end + directory "cookbooks/cookbook2" do + file "metadata.rb", cb_metadata("cookbook2", "2.0.0") + file "recipes/default.rb", "" + end + + directory "data_bags" do + directory "bag1" do + file "item1.json", {} + file "item2.json", {} + end + directory "bag2" do + file "item1.json", {} + file "item2.json", {} + end + end + + file "environments/environment1.json", {} + file "environments/environment2.json", {} + file "nodes/node1.json", {} + file "nodes/node2.json", {} + + file "roles/role1.json", {} + file "roles/role2.json", {} + file "users/user1.json", {} + file "users/user2.json", {} + end + + it "knife list -Rfp / returns everything" do + knife("list -Rp --local --flat /").should_succeed <<~EOM + /clients/ + /clients/client1.json + /clients/client2.json + /cookbooks/ + /cookbooks/cookbook1/ + /cookbooks/cookbook1/metadata.rb + /cookbooks/cookbook2/ + /cookbooks/cookbook2/metadata.rb + /cookbooks/cookbook2/recipes/ + /cookbooks/cookbook2/recipes/default.rb + /data_bags/ + /data_bags/bag1/ + /data_bags/bag1/item1.json + /data_bags/bag1/item2.json + /data_bags/bag2/ + /data_bags/bag2/item1.json + /data_bags/bag2/item2.json + /environments/ + /environments/environment1.json + /environments/environment2.json + /nodes/ + /nodes/node1.json + /nodes/node2.json + /roles/ + /roles/role1.json + /roles/role2.json + /users/ + /users/user1.json + /users/user2.json + EOM + end + + context "missing file/directory tests" do + it "knife list --local /blarghle reports missing directory" do + knife("list --local /blarghle").should_fail "ERROR: /blarghle: No such file or directory\n" + end + + it "knife list /roles/blarghle reports missing directory" do + knife("list --local /roles/blarghle").should_fail "ERROR: /roles/blarghle: No such file or directory\n" + end + + it "knife list /roles/blarghle/blorghle reports missing directory" do + knife("list --local /roles/blarghle/blorghle").should_fail "ERROR: /roles/blarghle/blorghle: No such file or directory\n" + end + end + end + end + + when_the_chef_server "is in Enterprise mode", osc_compat: false, single_org: false do + before do + organization "foo" + end + + before :each do + Chef::Config.chef_server_url = URI.join(Chef::Config.chef_server_url, "/organizations/foo") + end + + context "and is empty" do + it "knife list / returns all top level directories" do + knife("list /").should_succeed <<~EOM + /acls + /clients + /containers + /cookbook_artifacts + /cookbooks + /data_bags + /environments + /groups + /invitations.json + /members.json + /nodes + /org.json + /policies + /policy_groups + /roles + EOM + end + + it "knife list -R / returns everything" do + knife("list -R /").should_succeed <<~EOM + /: + acls + clients + containers + cookbook_artifacts + cookbooks + data_bags + environments + groups + invitations.json + members.json + nodes + org.json + policies + policy_groups + roles + + /acls: + clients + containers + cookbook_artifacts + cookbooks + data_bags + environments + groups + nodes + organization.json + policies + policy_groups + roles + + /acls/clients: + foo-validator.json + + /acls/containers: + clients.json + containers.json + cookbook_artifacts.json + cookbooks.json + data.json + environments.json + groups.json + nodes.json + policies.json + policy_groups.json + roles.json + sandboxes.json + + /acls/cookbook_artifacts: + + /acls/cookbooks: + + /acls/data_bags: + + /acls/environments: + _default.json + + /acls/groups: + admins.json + billing-admins.json + clients.json + users.json + + /acls/nodes: + + /acls/policies: + + /acls/policy_groups: + + /acls/roles: + + /clients: + foo-validator.json + + /containers: + clients.json + containers.json + cookbook_artifacts.json + cookbooks.json + data.json + environments.json + groups.json + nodes.json + policies.json + policy_groups.json + roles.json + sandboxes.json + + /cookbook_artifacts: + + /cookbooks: + + /data_bags: + + /environments: + _default.json + + /groups: + admins.json + billing-admins.json + clients.json + users.json + + /nodes: + + /policies: + + /policy_groups: + + /roles: + EOM + end + end + + it "knife list -R / returns everything" do + knife("list -R /").should_succeed <<~EOM + /: + acls + clients + containers + cookbook_artifacts + cookbooks + data_bags + environments + groups + invitations.json + members.json + nodes + org.json + policies + policy_groups + roles + + /acls: + clients + containers + cookbook_artifacts + cookbooks + data_bags + environments + groups + nodes + organization.json + policies + policy_groups + roles + + /acls/clients: + foo-validator.json + + /acls/containers: + clients.json + containers.json + cookbook_artifacts.json + cookbooks.json + data.json + environments.json + groups.json + nodes.json + policies.json + policy_groups.json + roles.json + sandboxes.json + + /acls/cookbook_artifacts: + + /acls/cookbooks: + + /acls/data_bags: + + /acls/environments: + _default.json + + /acls/groups: + admins.json + billing-admins.json + clients.json + users.json + + /acls/nodes: + + /acls/policies: + + /acls/policy_groups: + + /acls/roles: + + /clients: + foo-validator.json + + /containers: + clients.json + containers.json + cookbook_artifacts.json + cookbooks.json + data.json + environments.json + groups.json + nodes.json + policies.json + policy_groups.json + roles.json + sandboxes.json + + /cookbook_artifacts: + + /cookbooks: + + /data_bags: + + /environments: + _default.json + + /groups: + admins.json + billing-admins.json + clients.json + users.json + + /nodes: + + /policies: + + /policy_groups: + + /roles: + EOM + end + + context "has plenty of stuff in it" do + before do + client "client1", {} + client "client2", {} + container "container1", {} + container "container2", {} + cookbook "cookbook1", "1.0.0" + cookbook "cookbook2", "1.0.1", { "recipes" => { "default.rb" => "" } } + cookbook_artifact "cookbook_artifact1", "1x1" + cookbook_artifact "cookbook_artifact2", "2x2", { "recipes" => { "default.rb" => "" } } + data_bag "bag1", { "item1" => {}, "item2" => {} } + data_bag "bag2", { "item1" => {}, "item2" => {} } + environment "environment1", {} + environment "environment2", {} + group "group1", {} + group "group2", {} + node "node1", {} + node "node2", {} + org_invite "user1" + org_member "user2" + policy "policy1", "1.2.3", {} + policy "policy2", "1.2.3", {} + policy "policy2", "1.3.5", {} + policy_group "policy_group1", { "policies" => { "policy1" => { "revision_id" => "1.2.3" } } } + policy_group "policy_group2", { "policies" => { "policy2" => { "revision_id" => "1.3.5" } } } + role "role1", {} + role "role2", {} + user "user1", {} + user "user2", {} + end + + it "knife list -Rfp / returns everything" do + knife("list -Rfp /").should_succeed <<~EOM + /acls/ + /acls/clients/ + /acls/clients/client1.json + /acls/clients/client2.json + /acls/clients/foo-validator.json + /acls/containers/ + /acls/containers/clients.json + /acls/containers/container1.json + /acls/containers/container2.json + /acls/containers/containers.json + /acls/containers/cookbook_artifacts.json + /acls/containers/cookbooks.json + /acls/containers/data.json + /acls/containers/environments.json + /acls/containers/groups.json + /acls/containers/nodes.json + /acls/containers/policies.json + /acls/containers/policy_groups.json + /acls/containers/roles.json + /acls/containers/sandboxes.json + /acls/cookbook_artifacts/ + /acls/cookbook_artifacts/cookbook_artifact1.json + /acls/cookbook_artifacts/cookbook_artifact2.json + /acls/cookbooks/ + /acls/cookbooks/cookbook1.json + /acls/cookbooks/cookbook2.json + /acls/data_bags/ + /acls/data_bags/bag1.json + /acls/data_bags/bag2.json + /acls/environments/ + /acls/environments/_default.json + /acls/environments/environment1.json + /acls/environments/environment2.json + /acls/groups/ + /acls/groups/admins.json + /acls/groups/billing-admins.json + /acls/groups/clients.json + /acls/groups/group1.json + /acls/groups/group2.json + /acls/groups/users.json + /acls/nodes/ + /acls/nodes/node1.json + /acls/nodes/node2.json + /acls/organization.json + /acls/policies/ + /acls/policies/policy1.json + /acls/policies/policy2.json + /acls/policy_groups/ + /acls/policy_groups/policy_group1.json + /acls/policy_groups/policy_group2.json + /acls/roles/ + /acls/roles/role1.json + /acls/roles/role2.json + /clients/ + /clients/client1.json + /clients/client2.json + /clients/foo-validator.json + /containers/ + /containers/clients.json + /containers/container1.json + /containers/container2.json + /containers/containers.json + /containers/cookbook_artifacts.json + /containers/cookbooks.json + /containers/data.json + /containers/environments.json + /containers/groups.json + /containers/nodes.json + /containers/policies.json + /containers/policy_groups.json + /containers/roles.json + /containers/sandboxes.json + /cookbook_artifacts/ + /cookbook_artifacts/cookbook_artifact1-1x1/ + /cookbook_artifacts/cookbook_artifact1-1x1/metadata.rb + /cookbook_artifacts/cookbook_artifact2-2x2/ + /cookbook_artifacts/cookbook_artifact2-2x2/metadata.rb + /cookbook_artifacts/cookbook_artifact2-2x2/recipes/ + /cookbook_artifacts/cookbook_artifact2-2x2/recipes/default.rb + /cookbooks/ + /cookbooks/cookbook1/ + /cookbooks/cookbook1/metadata.rb + /cookbooks/cookbook2/ + /cookbooks/cookbook2/metadata.rb + /cookbooks/cookbook2/recipes/ + /cookbooks/cookbook2/recipes/default.rb + /data_bags/ + /data_bags/bag1/ + /data_bags/bag1/item1.json + /data_bags/bag1/item2.json + /data_bags/bag2/ + /data_bags/bag2/item1.json + /data_bags/bag2/item2.json + /environments/ + /environments/_default.json + /environments/environment1.json + /environments/environment2.json + /groups/ + /groups/admins.json + /groups/billing-admins.json + /groups/clients.json + /groups/group1.json + /groups/group2.json + /groups/users.json + /invitations.json + /members.json + /nodes/ + /nodes/node1.json + /nodes/node2.json + /org.json + /policies/ + /policies/policy1-1.2.3.json + /policies/policy2-1.2.3.json + /policies/policy2-1.3.5.json + /policy_groups/ + /policy_groups/policy_group1.json + /policy_groups/policy_group2.json + /roles/ + /roles/role1.json + /roles/role2.json + EOM + end + end + end +end diff --git a/knife/spec/integration/node_bulk_delete_spec.rb b/knife/spec/integration/node_bulk_delete_spec.rb new file mode 100644 index 0000000000..8784b5ea8a --- /dev/null +++ b/knife/spec/integration/node_bulk_delete_spec.rb @@ -0,0 +1,52 @@ +# +# 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 "support/shared/context/config" + +describe "knife node bulk delete", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has some nodes" do + before do + node "cons", {} + node "car", {} + node "cdr", {} + node "cat", {} + end + + it "deletes all matching nodes" do + knife("node bulk delete ^ca.*", input: "Y").should_succeed <<~EOM + The following nodes will be deleted: + + car cat + + Are you sure you want to delete these nodes? (Y/N) Deleted node car + Deleted node cat + EOM + + knife("node list").should_succeed <<~EOM + cdr + cons + EOM + end + end + +end diff --git a/knife/spec/integration/node_create_spec.rb b/knife/spec/integration/node_create_spec.rb new file mode 100644 index 0000000000..d3debb8f00 --- /dev/null +++ b/knife/spec/integration/node_create_spec.rb @@ -0,0 +1,47 @@ +# +# 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 "support/shared/context/config" +require "openssl" + +describe "knife node create", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + let(:out) { "Created node[bah]\n" } + + when_the_chef_server "is empty" do + it "creates a new node" do + knife("node create bah").should_succeed out + end + + it "creates a new validator node" do + knife("node create bah").should_succeed out + knife("node show bah").should_succeed(/Node Name: bah/) + end + + it "refuses to add an existing node" do + pending "Knife node create must not blindly overwrite an existing node" + knife("node create bah").should_succeed out + expect { knife("node create bah") }.to raise_error(Net::HTTPClientException) + end + + end +end diff --git a/knife/spec/integration/node_delete_spec.rb b/knife/spec/integration/node_delete_spec.rb new file mode 100644 index 0000000000..3cece6ebaf --- /dev/null +++ b/knife/spec/integration/node_delete_spec.rb @@ -0,0 +1,48 @@ +# +# 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 "support/shared/context/config" + +describe "knife node delete", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has some nodes" do + before do + node "cons", {} + node "car", {} + node "cdr", {} + node "cat", {} + end + + it "deletes a node" do + knife("node delete car", input: "Y").should_succeed <<~EOM + Do you really want to delete car? (Y/N) Deleted node[car] + EOM + + knife("node list").should_succeed <<~EOM + cat + cdr + cons + EOM + end + + end +end diff --git a/knife/spec/integration/node_environment_set_spec.rb b/knife/spec/integration/node_environment_set_spec.rb new file mode 100644 index 0000000000..51b288fe39 --- /dev/null +++ b/knife/spec/integration/node_environment_set_spec.rb @@ -0,0 +1,46 @@ +# +# 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 "support/shared/context/config" + +describe "knife node environment set", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has a node and an environment" do + before do + node "cons", {} + environment "lisp", {} + end + + it "sets an environment on a node" do + knife("node environment set cons lisp").should_succeed(/chef_environment:.*lisp/) + knife("node show cons -a chef_environment").should_succeed <<~EOM + cons: + chef_environment: lisp + EOM + end + + it "with no environment" do + knife("node environment set adam").should_fail stderr: "FATAL: You must specify a node name and an environment.\n", + stdout: /^USAGE: knife node environment set NODE ENVIRONMENT\n/ + end + end +end diff --git a/knife/spec/integration/node_from_file_spec.rb b/knife/spec/integration/node_from_file_spec.rb new file mode 100644 index 0000000000..5dcaaaa463 --- /dev/null +++ b/knife/spec/integration/node_from_file_spec.rb @@ -0,0 +1,59 @@ +# +# 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 "support/shared/context/config" + +describe "knife node from file", :workstation do + include IntegrationSupport + include KnifeSupport + + # include_context "default config options" + + let(:node_dir) { "#{@repository_dir}/nodes" } + + when_the_chef_server "is empty" do + when_the_repository "has some nodes" do + before do + + file "nodes/cons.json", <<~EOM + { + "name": "cons", + "chef_environment": "_default", + "run_list": [ + "recipe[cons]" + ] + , + "normal": { + "tags": [ + + ] + } + } + EOM + + end + + it "uploads a single file" do + knife("node from file #{node_dir}/cons.json").should_succeed stderr: <<~EOM + Updated Node cons + EOM + end + + end + end +end diff --git a/knife/spec/integration/node_list_spec.rb b/knife/spec/integration/node_list_spec.rb new file mode 100644 index 0000000000..65c201be3f --- /dev/null +++ b/knife/spec/integration/node_list_spec.rb @@ -0,0 +1,45 @@ +# +# 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 "support/shared/context/config" + +describe "knife node list", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has some nodes" do + before do + node "cons", {} + node "car", {} + node "cdr", {} + node "cat", {} + end + + it "lists all cookbooks" do + knife("node list").should_succeed <<~EOM + car + cat + cdr + cons + EOM + end + + end +end diff --git a/knife/spec/integration/node_run_list_add_spec.rb b/knife/spec/integration/node_run_list_add_spec.rb new file mode 100644 index 0000000000..72b5328b17 --- /dev/null +++ b/knife/spec/integration/node_run_list_add_spec.rb @@ -0,0 +1,54 @@ +# +# 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 "support/shared/context/config" + +describe "knife node run list add", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has a node with no run_list" do + before do + node "cons", {} + end + + it "sets the run list" do + knife("node run list add cons recipe[foo]").should_succeed(/run_list:\s*recipe\[foo\]\n/) + end + end + + when_the_chef_server "has a node with a run_list" do + before do + node "cons", { run_list: ["recipe[bar]"] } + end + + it "appends to the run list" do + knife("node run list add cons recipe[foo]").should_succeed(/run_list:\n\s*recipe\[bar\]\n\s*recipe\[foo\]\n/m) + end + + it "adds to the run list before the specified item" do + knife("node run list add cons -b recipe[bar] recipe[foo]").should_succeed(/run_list:\n\s*recipe\[foo\]\n\s*recipe\[bar\]\n/m) + end + + it "adds to the run list after the specified item" do + knife("node run list add cons -a recipe[bar] recipe[foo]").should_succeed(/run_list:\n\s*recipe\[bar\]\n\s*recipe\[foo\]\n/m) + end + end +end diff --git a/knife/spec/integration/node_run_list_remove_spec.rb b/knife/spec/integration/node_run_list_remove_spec.rb new file mode 100644 index 0000000000..19aeb81806 --- /dev/null +++ b/knife/spec/integration/node_run_list_remove_spec.rb @@ -0,0 +1,36 @@ +# +# 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 "support/shared/context/config" + +describe "knife node run list remove", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has a node with a run_list" do + before do + node "cons", { run_list: ["recipe[bar]", "recipe[foo]"] } + end + + it "removes the item from the run list" do + knife("node run list remove cons recipe[bar]").should_succeed(/run_list:\s*recipe\[foo\]\n/m) + end + end +end diff --git a/knife/spec/integration/node_run_list_set_spec.rb b/knife/spec/integration/node_run_list_set_spec.rb new file mode 100644 index 0000000000..d83e74dd04 --- /dev/null +++ b/knife/spec/integration/node_run_list_set_spec.rb @@ -0,0 +1,41 @@ +# +# 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 "support/shared/context/config" + +describe "knife node run list set", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has a node with a run_list" do + before do + node "cons", { run_list: ["recipe[bar]", "recipe[foo]"] } + end + + it "sets the run list" do + knife("node run list set cons recipe[bar]").should_succeed(/run_list:\s*recipe\[bar\]\n/m) + end + + it "with no role or recipe" do + knife("node run list set cons").should_fail stderr: "FATAL: You must supply both a node name and a run list.\n", + stdout: /^USAGE: knife node run_list set NODE ENTRIES \(options\)/m + end + end +end diff --git a/knife/spec/integration/node_show_spec.rb b/knife/spec/integration/node_show_spec.rb new file mode 100644 index 0000000000..be63011ef8 --- /dev/null +++ b/knife/spec/integration/node_show_spec.rb @@ -0,0 +1,36 @@ +# +# 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 "support/shared/context/config" + +describe "knife node show", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has a node with a run_list" do + before do + node "cons", { run_list: ["recipe[bar]", "recipe[foo]"] } + end + + it "shows the node" do + knife("node show cons").should_succeed(/Run List:\s*recipe\[bar\], recipe\[foo\]\n/m) + end + end +end diff --git a/knife/spec/integration/raw_spec.rb b/knife/spec/integration/raw_spec.rb new file mode 100644 index 0000000000..8e7e913b02 --- /dev/null +++ b/knife/spec/integration/raw_spec.rb @@ -0,0 +1,297 @@ +# +# 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 "support/shared/context/config" +require "chef/knife/raw" +require "chef/knife/show" +require "tiny_server" + +describe "knife raw", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + 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 + + it "knife raw /nodes/x returns the node" do + knife("raw /nodes/x").should_succeed <<~EOM + { + "name": "x", + "json_class": "Chef::Node", + "chef_type": "node", + "chef_environment": "_default", + "override": { + + }, + "normal": { + "tags": [ + + ] + }, + "default": { + + }, + "automatic": { + + }, + "run_list": [ + + ] + } + EOM + end + + it "knife raw /blarghle returns 404" do + knife("raw /blarghle").should_fail(/ERROR: Server responded with error 404 "Not Found\s*"/) + end + + it "knife raw -m DELETE /roles/x succeeds" do + knife("raw -m DELETE /roles/x").should_succeed <<~EOM + { + "name": "x", + "description": "", + "json_class": "Chef::Role", + "chef_type": "role", + "default_attributes": { + + }, + "override_attributes": { + + }, + "run_list": [ + + ], + "env_run_lists": { + + } + } + EOM + knife("show /roles/x.json").should_fail "ERROR: /roles/x.json: No such file or directory\n" + end + + it "knife raw -m PUT -i blah.txt /roles/x succeeds" do + Tempfile.open("raw_put_input") do |file| + file.write <<~EOM + { + "name": "x", + "description": "eek", + "json_class": "Chef::Role", + "chef_type": "role", + "default_attributes": { + + }, + "override_attributes": { + + }, + "run_list": [ + + ], + "env_run_lists": { + + } + } + EOM + file.close + + knife("raw -m PUT -i #{file.path} /roles/x").should_succeed <<~EOM + { + "name": "x", + "description": "eek", + "json_class": "Chef::Role", + "chef_type": "role", + "default_attributes": { + + }, + "override_attributes": { + + }, + "run_list": [ + + ], + "env_run_lists": { + + } + } + EOM + knife("show /roles/x.json").should_succeed <<~EOM + /roles/x.json: + { + "name": "x", + "description": "eek", + "json_class": "Chef::Role", + "chef_type": "role", + "default_attributes": { + + }, + "override_attributes": { + + }, + "run_list": [ + + ], + "env_run_lists": { + + } + } + EOM + end + end + + it "knife raw -m POST -i blah.txt /roles succeeds" do + Tempfile.open("raw_put_input") do |file| + file.write <<~EOM + { + "name": "y", + "description": "eek", + "json_class": "Chef::Role", + "chef_type": "role", + "default_attributes": { + }, + "override_attributes": { + }, + "run_list": [ + + ], + "env_run_lists": { + } + } + EOM + file.close + + knife("raw -m POST -i #{file.path} /roles").should_succeed <<~EOM + { + "uri": "#{Chef::Config.chef_server_url}/roles/y" + } + EOM + knife("show /roles/y.json").should_succeed <<~EOM + /roles/y.json: + { + "name": "y", + "description": "eek", + "json_class": "Chef::Role", + "chef_type": "role", + "default_attributes": { + + }, + "override_attributes": { + + }, + "run_list": [ + + ], + "env_run_lists": { + + } + } + EOM + end + end + + context "When a server returns raw json" do + def start_tiny_server(**server_opts) + @server = TinyServer::Manager.new(**server_opts) + @server.start + @api = TinyServer::API.instance + @api.clear + + @api.get("/blah", 200, nil, { "Content-Type" => "application/json" }) do + '{ "x": "y", "a": "b" }' + end + end + + def stop_tiny_server + @server.stop + @server = @api = nil + end + + before :each do + Chef::Config.chef_server_url = "http://localhost:9000" + start_tiny_server + end + + after :each do + stop_tiny_server + end + + it "knife raw /blah returns the prettified json" do + knife("raw /blah").should_succeed <<~EOM + { + "x": "y", + "a": "b" + } + EOM + end + + it "knife raw --no-pretty /blah returns the raw json" do + knife("raw --no-pretty /blah").should_succeed <<~EOM + { "x": "y", "a": "b" } + EOM + end + end + + context "When a server returns text" do + def start_tiny_server(**server_opts) + @server = TinyServer::Manager.new(**server_opts) + @server.start + @api = TinyServer::API.instance + @api.clear + + @api.get("/blah", 200, nil, { "Content-Type" => "text" }) do + '{ "x": "y", "a": "b" }' + end + end + + def stop_tiny_server + @server.stop + @server = @api = nil + end + + before :each do + Chef::Config.chef_server_url = "http://localhost:9000" + start_tiny_server + end + + after :each do + stop_tiny_server + end + + it "knife raw /blah returns the raw text" do + knife("raw /blah").should_succeed(<<~EOM) + { "x": "y", "a": "b" } + EOM + end + + it "knife raw --no-pretty /blah returns the raw text" do + knife("raw --no-pretty /blah").should_succeed(<<~EOM) + { "x": "y", "a": "b" } + EOM + end + end + end +end diff --git a/knife/spec/integration/redirection_spec.rb b/knife/spec/integration/redirection_spec.rb new file mode 100644 index 0000000000..eea5556cff --- /dev/null +++ b/knife/spec/integration/redirection_spec.rb @@ -0,0 +1,64 @@ +# +# 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 "tiny_server" +require "support/shared/integration/integration_helper" +require "support/shared/context/config" +require "chef/knife/list" + +describe "redirection", :workstation do + include IntegrationSupport + include KnifeSupport + + def start_tiny_server(real_chef_server_url, **server_opts) + @server = TinyServer::Manager.new(**server_opts) + @server.start + @api = TinyServer::API.instance + @api.clear + + @api.get("/roles", 302, nil, { "Content-Type" => "text", "Location" => "#{real_chef_server_url}/roles" }) do + end + end + + def stop_tiny_server + @server.stop + @server = @api = nil + end + + include_context "default config options" + + when_the_chef_server "has a role" do + before { role "x", {} } + + context "and another server redirects to it with 302" do + before(:each) do + real_chef_server_url = Chef::Config.chef_server_url + Chef::Config.chef_server_url = "http://localhost:9000" + start_tiny_server(real_chef_server_url) + end + + after(:each) do + stop_tiny_server + end + + it "knife list /roles returns the role" do + knife("list /roles").should_succeed "/roles/x.json\n" + end + end + end +end diff --git a/knife/spec/integration/role_bulk_delete_spec.rb b/knife/spec/integration/role_bulk_delete_spec.rb new file mode 100644 index 0000000000..76745d9b6a --- /dev/null +++ b/knife/spec/integration/role_bulk_delete_spec.rb @@ -0,0 +1,52 @@ +# +# 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 "support/shared/context/config" + +describe "knife role bulk delete", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has some roles" do + before do + role "cons", {} + role "car", {} + role "cdr", {} + role "cat", {} + end + + it "deletes all matching roles" do + knife("role bulk delete ^ca.*", input: "Y").should_succeed <<~EOM + The following roles will be deleted: + + car cat + + Are you sure you want to delete these roles? (Y/N) Deleted role car + Deleted role cat + EOM + + knife("role list").should_succeed <<~EOM + cdr + cons + EOM + end + + end +end diff --git a/knife/spec/integration/role_create_spec.rb b/knife/spec/integration/role_create_spec.rb new file mode 100644 index 0000000000..03f59d4b99 --- /dev/null +++ b/knife/spec/integration/role_create_spec.rb @@ -0,0 +1,41 @@ +# +# 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 "support/shared/context/config" + +describe "knife role create", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + let(:out) { "Created role[bah]\n" } + + when_the_chef_server "is empty" do + it "creates a new role" do + knife("role create bah").should_succeed out + end + + it "refuses to add an existing role" do + pending "Knife role create must not blindly overwrite an existing role" + knife("role create bah").should_succeed out + expect { knife("role create bah") }.to raise_error(Net::HTTPClientException) + end + + end +end diff --git a/knife/spec/integration/role_delete_spec.rb b/knife/spec/integration/role_delete_spec.rb new file mode 100644 index 0000000000..22b36e5572 --- /dev/null +++ b/knife/spec/integration/role_delete_spec.rb @@ -0,0 +1,48 @@ +# +# 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 "support/shared/context/config" + +describe "knife role delete", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has some roles" do + before do + role "cons", {} + role "car", {} + role "cdr", {} + role "cat", {} + end + + it "deletes a role" do + knife("role delete car", input: "Y").should_succeed <<~EOM + Do you really want to delete car? (Y/N) Deleted role[car] + EOM + + knife("role list").should_succeed <<~EOM + cat + cdr + cons + EOM + end + + end +end diff --git a/knife/spec/integration/role_from_file_spec.rb b/knife/spec/integration/role_from_file_spec.rb new file mode 100644 index 0000000000..ae296122ce --- /dev/null +++ b/knife/spec/integration/role_from_file_spec.rb @@ -0,0 +1,96 @@ +# +# 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 "support/shared/context/config" + +describe "knife role from file", :workstation do + include IntegrationSupport + include KnifeSupport + + # include_context "default config options" + + let(:role_dir) { "#{@repository_dir}/roles" } + + when_the_chef_server "is empty" do + when_the_repository "has some roles" do + before do + + file "roles/cons.json", <<~EOM + { + "name": "cons", + "description": "An role", + "json_class": "Chef::role", + "chef_type": "role", + "default_attributes": { + "hola": "Amigos!" + }, + "override_attributes": { + + } + } + EOM + + file "roles/car.json", <<~EOM + { + "name": "car", + "description": "A role for list nodes", + "json_class": "Chef::Role", + "chef_type": "role", + "default_attributes": { + "hola": "Amigos!" + }, + "override_attributes": { + + } + } + EOM + + file "roles/cdr.json", <<~EOM + { + "name": "cdr", + "description": "A role for last nodes", + "json_class": "Chef::Role", + "chef_type": "role", + "default_attributes": { + "hola": "Amigos!" + }, + "override_attributes": { + + } + } + EOM + + end + + it "uploads a single file" do + knife("role from file #{role_dir}/cons.json").should_succeed stderr: <<~EOM + Updated Role cons + EOM + end + + it "uploads many files" do + knife("role from file #{role_dir}/cons.json #{role_dir}/car.json #{role_dir}/cdr.json").should_succeed stderr: <<~EOM + Updated Role cons + Updated Role car + Updated Role cdr + EOM + end + + end + end +end diff --git a/knife/spec/integration/role_list_spec.rb b/knife/spec/integration/role_list_spec.rb new file mode 100644 index 0000000000..39aa28783f --- /dev/null +++ b/knife/spec/integration/role_list_spec.rb @@ -0,0 +1,45 @@ +# +# 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 "support/shared/context/config" + +describe "knife role list", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has some roles" do + before do + role "cons", {} + role "car", {} + role "cdr", {} + role "cat", {} + end + + it "lists all cookbooks" do + knife("role list").should_succeed <<~EOM + car + cat + cdr + cons + EOM + end + + end +end diff --git a/knife/spec/integration/role_show_spec.rb b/knife/spec/integration/role_show_spec.rb new file mode 100644 index 0000000000..a4ecea1d61 --- /dev/null +++ b/knife/spec/integration/role_show_spec.rb @@ -0,0 +1,51 @@ +# +# 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 "support/shared/context/config" + +describe "knife role show", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has some roles" do + before do + role "cons", {} + role "car", {} + role "cdr", {} + role "cat", {} + end + + # rubocop:disable Layout/TrailingWhitespace + it "shows a cookbook" do + knife("role show cons").should_succeed <<~EOM + chef_type: role + default_attributes: + description: + env_run_lists: + json_class: Chef::Role + name: cons + override_attributes: + run_list: + EOM + end + # rubocop:enable Layout/TrailingWhitespace + + end +end diff --git a/knife/spec/integration/search_node_spec.rb b/knife/spec/integration/search_node_spec.rb new file mode 100644 index 0000000000..9e7935b83c --- /dev/null +++ b/knife/spec/integration/search_node_spec.rb @@ -0,0 +1,40 @@ +# +# 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 "support/shared/context/config" + +describe "knife node show", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has a node with a run_list" do + before do + node "cons", { run_list: ["recipe[bar]", "recipe[foo]"] } + end + + it "finds the node" do + knife("search node name:cons").should_succeed(/Node Name:\s*cons/, stderr: "1 items found\n\n") + end + + it "does not find a node" do + knife("search node name:snoc").should_fail("", stderr: "0 items found\n\n", exit_code: 1) + end + end +end diff --git a/knife/spec/integration/serve_spec.rb b/knife/spec/integration/serve_spec.rb new file mode 100644 index 0000000000..fa9b1dc47c --- /dev/null +++ b/knife/spec/integration/serve_spec.rb @@ -0,0 +1,92 @@ +# +# 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 "support/shared/integration/integration_helper" +require "chef/knife/serve" +require "chef/server_api" + +describe "knife serve", :workstation do + include IntegrationSupport + include KnifeSupport + + def with_knife_serve + exception = nil + t = Thread.new do + + knife("serve --chef-zero-port=8890") + rescue + exception = $! + + end + begin + Chef::Config.log_level = :debug + Chef::Config.chef_server_url = "http://localhost:8890" + Chef::Config.node_name = nil + Chef::Config.client_key = nil + api = Chef::ServerAPI.new + yield api + rescue + if exception + raise exception + else + raise + end + ensure + t.kill + sleep 0.5 + end + end + + when_the_repository "also has one of each thing" do + before do + file "nodes/a_node_in_json.json", { "foo" => "bar" } + file "nodes/a_node_in_ruby.rb", "name 'a_node_in_ruby'" + file "roles/a_role_in_json.json", { "foo" => "bar" } + file "roles/a_role_in_ruby.rb", "name 'a_role_in_ruby'" + end + + %w{a_node_in_json a_node_in_ruby}.each do |file_type| + context file_type do + it "knife serve serves up /nodes" do + with_knife_serve do |api| + expect(api.get("nodes")).to have_key(file_type) + end + end + it "knife serve serves up /nodes/#{file_type}" do + with_knife_serve do |api| + expect(api.get("nodes/#{file_type}")["name"]).to eq(file_type) + end + end + end + end + + %w{a_role_in_json a_role_in_ruby}.each do |file_type| + context file_type do + it "knife serve serves up /roles" do + with_knife_serve do |api| + expect(api.get("roles")).to have_key(file_type) + end + end + it "knife serve serves up /roles/#{file_type}" do + with_knife_serve do |api| + expect(api.get("roles/#{file_type}")["name"]).to eq(file_type) + end + end + end + end + end +end diff --git a/knife/spec/integration/show_spec.rb b/knife/spec/integration/show_spec.rb new file mode 100644 index 0000000000..6913494916 --- /dev/null +++ b/knife/spec/integration/show_spec.rb @@ -0,0 +1,197 @@ +# +# 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 "support/shared/context/config" +require "chef/knife/show" + +describe "knife show", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + 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 "also has one of each thing" do + before do + file "clients/x.json", { "foo" => "bar" } + file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0") + file "data_bags/x/y.json", { "foo" => "bar" } + file "environments/_default.json", { "foo" => "bar" } + file "environments/x.json", { "foo" => "bar" } + file "nodes/x.json", { "foo" => "bar" } + file "roles/x.json", { "foo" => "bar" } + file "users/x.json", { "foo" => "bar" } + end + + it "knife show /cookbooks/x/metadata.rb shows the remote version" do + knife("show /cookbooks/x/metadata.rb").should_succeed <<~EOM + /cookbooks/x/metadata.rb: + name "x"; version "1.0.0" + EOM + end + it "knife show --local /cookbooks/x/metadata.rb shows the local version" do + knife("show --local /cookbooks/x/metadata.rb").should_succeed <<~EOM + /cookbooks/x/metadata.rb: + name "x"; version "1.0.0" + EOM + end + it "knife show /data_bags/x/y.json shows the remote version" do + knife("show /data_bags/x/y.json").should_succeed <<~EOM + /data_bags/x/y.json: + { + "id": "y" + } + EOM + end + it "knife show --local /data_bags/x/y.json shows the local version" do + knife("show --local /data_bags/x/y.json").should_succeed <<~EOM + /data_bags/x/y.json: + { + "foo": "bar" + } + EOM + end + it "knife show /environments/x.json shows the remote version" do + knife("show /environments/x.json").should_succeed <<~EOM + /environments/x.json: + { + "name": "x", + "description": "", + "cookbook_versions": { + + }, + "default_attributes": { + + }, + "override_attributes": { + + }, + "json_class": "Chef::Environment", + "chef_type": "environment" + } + EOM + end + it "knife show --local /environments/x.json shows the local version" do + knife("show --local /environments/x.json").should_succeed <<~EOM + /environments/x.json: + { + "foo": "bar" + } + EOM + end + it "knife show /roles/x.json shows the remote version" do + knife("show /roles/x.json").should_succeed <<~EOM + /roles/x.json: + { + "name": "x", + "description": "", + "json_class": "Chef::Role", + "chef_type": "role", + "default_attributes": { + + }, + "override_attributes": { + + }, + "run_list": [ + + ], + "env_run_lists": { + + } + } + EOM + end + it "knife show --local /roles/x.json shows the local version" do + knife("show --local /roles/x.json").should_succeed <<~EOM + /roles/x.json: + { + "foo": "bar" + } + EOM + end + # show directory + it "knife show /data_bags/x fails" do + knife("show /data_bags/x").should_fail "ERROR: /data_bags/x: is a directory\n" + end + it "knife show --local /data_bags/x fails" do + knife("show --local /data_bags/x").should_fail "ERROR: /data_bags/x: is a directory\n" + end + # show nonexistent file + it "knife show /environments/nonexistent.json fails" do + knife("show /environments/nonexistent.json").should_fail "ERROR: /environments/nonexistent.json: No such file or directory\n" + end + it "knife show --local /environments/nonexistent.json fails" do + knife("show --local /environments/nonexistent.json").should_fail "ERROR: /environments/nonexistent.json: No such file or directory\n" + end + end + end + + when_the_chef_server "has a hash with multiple keys" do + before do + environment "x", { + "default_attributes" => { "foo" => "bar" }, + "cookbook_versions" => { "blah" => "= 1.0.0" }, + "override_attributes" => { "x" => "y" }, + "description" => "woo", + "name" => "x", + } + end + it "knife show shows the attributes in a predetermined order" do + knife("show /environments/x.json").should_succeed <<~EOM + /environments/x.json: + { + "name": "x", + "description": "woo", + "cookbook_versions": { + "blah": "= 1.0.0" + }, + "default_attributes": { + "foo": "bar" + }, + "override_attributes": { + "x": "y" + }, + "json_class": "Chef::Environment", + "chef_type": "environment" + } + EOM + end + end + + when_the_repository "has an environment with bad JSON" do + before { file "environments/x.json", "{" } + it "knife show succeeds" do + knife("show --local /environments/x.json").should_succeed <<~EOM + /environments/x.json: + { + EOM + end + end +end diff --git a/knife/spec/integration/upload_spec.rb b/knife/spec/integration/upload_spec.rb new file mode 100644 index 0000000000..e4bb44ad7e --- /dev/null +++ b/knife/spec/integration/upload_spec.rb @@ -0,0 +1,1617 @@ +# +# 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", {} + 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 <<~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 "x", "1.0.1", {} + policy "y", "1.0.0", {} + policy_group "x", { + "policies" => { + "x" => { "revision_id" => "1.0.1" }, + "y" => { "revision_id" => "1.0.0" }, + }, + } + role "x", { "run_list" => [ "blah" ] } + end + + it "knife upload updates everything" do + knife("upload /").should_succeed <<~EOM + Updated /acls/groups/blah.json + Updated /clients/x.json + Updated /cookbooks/x + Updated /data_bags/x/y.json + Updated /environments/x.json + Updated /groups/x.json + Updated /invitations.json + Updated /members.json + Updated /nodes/x.json + Updated /org.json + Created /policies/blah-1.0.0.json + Updated /policy_groups/x.json + Updated /roles/x.json + EOM + knife("diff --name-status --diff-filter=AMT /").should_succeed "" + end + end + end + + when_the_repository "has an org.json that does not change full_name" do + 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 diff --git a/knife/spec/knife_spec_helper.rb b/knife/spec/knife_spec_helper.rb new file mode 100644 index 0000000000..7395d2759b --- /dev/null +++ b/knife/spec/knife_spec_helper.rb @@ -0,0 +1,241 @@ +# +# Author:: Adam Jacob (<adam@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. + +# If you need to add anything in here, don't. +# Add it to one of the files in spec/support + +# Abuse ruby's constant lookup to avoid undefined constant errors + +$LOAD_PATH.unshift File.expand_path("..", __dir__) +$LOAD_PATH.unshift File.expand_path("../../chef-config/lib", __dir__) +$LOAD_PATH.unshift File.expand_path("../../chef-utils/lib", __dir__) + +require "rubygems" +require "rspec/mocks" +require "rexml/document" +require "webmock/rspec" + +require "chef/knife" + +# cwd is knife/ +Dir["lib/chef/knife/**/*.rb"] + .map { |f| f.gsub("lib/", "") } + .map { |f| f.gsub(/\.rb$/, "") } + .each { |f| require f } + +require "chef/resource_resolver" +require "chef/provider_resolver" + +require "chef/mixins" +require "chef/dsl" + +require "chef/shell" +require "chef/util/file_edit" + +require "chef/config" + +require "chef/chef_fs/file_system_cache" + +require "chef/api_client_v1" + +require "chef/mixin/versioned_api" +require "chef/server_api_versions" + +if ENV["CHEF_FIPS"] == "1" + Chef::Config.init_openssl +end + +# If you want to load anything into the testing environment +# without versioning it, add it to spec/support/local_gems.rb +require "spec/support/local_gems" if File.exist?(File.join(File.dirname(__FILE__), "support", "local_gems.rb")) + +# Explicitly require spec helpers that need to load first +require "spec/support/platform_helpers" +require "spec/support/shared/unit/mock_shellout" +require "spec/support/recipe_dsl_helper" +require "spec/support/key_helpers" +require "spec/support/shared/unit/knife_shared" +require "spec/support/shared/functional/knife" +require "spec/support/shared/integration/knife_support" +require "spec/support/shared/matchers/exit_with_code" +require "spec/support/shared/matchers/match_environment_variable" + +# Autoloads support files +# Excludes support/platforms by default +# Do not change the gsub. +Dir["spec/support/**/*.rb"] + .reject { |f| f =~ %r{^spec/support/platforms} } + .reject { |f| f =~ %r{^spec/support/pedant} } + .map { |f| f.gsub(/.rb$/, "") } + .map { |f| f.gsub(%r{spec/}, "") } + .each { |f| require f } + +OHAI_SYSTEM = Ohai::System.new +OHAI_SYSTEM.all_plugins(["platform", "hostname", "languages/powershell", "uptime"]) + +test_node = Chef::Node.new +test_node.automatic["os"] = (OHAI_SYSTEM["os"] || "unknown_os").dup.freeze +test_node.automatic["platform_family"] = (OHAI_SYSTEM["platform_family"] || "unknown_platform_family").dup.freeze +test_node.automatic["platform"] = (OHAI_SYSTEM["platform"] || "unknown_platform").dup.freeze +test_node.automatic["platform_version"] = (OHAI_SYSTEM["platform_version"] || "unknown_platform_version").dup.freeze +TEST_NODE = test_node.freeze +TEST_OS = TEST_NODE["os"] +TEST_PLATFORM = TEST_NODE["platform"] +TEST_PLATFORM_VERSION = TEST_NODE["platform_version"] +TEST_PLATFORM_FAMILY = TEST_NODE["platform_family"] + +provider_priority_map ||= nil +resource_priority_map ||= nil +provider_handler_map ||= nil +resource_handler_map ||= nil + +class UnexpectedSystemExit < RuntimeError + def self.from(system_exit) + new(system_exit.message).tap { |e| e.set_backtrace(system_exit.backtrace) } + end +end + +RSpec.configure do |config| + config.include(RSpec::Matchers) + config.include(MockShellout::RSpec) + config.filter_run focus: true + config.filter_run_excluding external: true + config.raise_on_warning = true + + # Explicitly disable :should syntax + # And set max_formatted_output_length to nil to prevent RSpec from doing truncation. + config.expect_with :rspec do |c| + c.syntax = :expect + c.max_formatted_output_length = nil + end + config.mock_with :rspec do |c| + c.syntax = :expect + c.allow_message_expectations_on_nil = false + end + + # TODO - which if any of these filters apply to knife tests? + # + # Only run these tests on platforms that are also chef workstations + config.filter_run_excluding :workstation if solaris? || aix? + + # Tests that randomly fail, but may have value. + config.filter_run_excluding volatile: true + config.filter_run_excluding volatile_on_solaris: true if solaris? + config.filter_run_excluding volatile_from_verify: false + + config.filter_run_excluding skip_buildkite: true if ENV["BUILDKITE"] + + config.filter_run_excluding windows_only: true unless windows? + config.filter_run_excluding unix_only: true unless unix? + + # check for particular binaries we need + + running_platform_arch = `uname -m`.strip unless windows? + + config.filter_run_excluding arch: lambda { |target_arch| + running_platform_arch != target_arch + } + + config.run_all_when_everything_filtered = true + + config.before(:each) do + # it'd be nice to run this with connections blocked or only to localhost, but we do make lots + # of real connections, so cannot. we reset it to allow connections every time to avoid + # tests setting connections to be disabled and that state leaking into other tests. + WebMock.allow_net_connect! + Chef.reset! + Chef::ChefFS::FileSystemCache.instance.reset! + Chef::Config.reset + Chef::Log.setup! + Chef::ServerAPIVersions.instance.reset! + Chef::Config[:log_level] = :fatal + Chef::Log.level(Chef::Config[:log_level]) + + # By default, treat deprecation warnings as errors in tests. + # and set environment variable so the setting persists in child processes + Chef::Config.treat_deprecation_warnings_as_errors(true) + ENV["CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS"] = "1" + end + + # This bit of jankiness guards against specs which accidentally drop privs when running as + # root -- which are nearly impossible to debug and so we bail out very hard if this + # condition ever happens. If a spec stubs Process.[e]uid this can throw a false positive + # which the spec must work around by unmocking Process.[e]uid to and_call_original in its + # after block. + # Should not be a problem with knife which does not escalate local privs, but + # it seems wise to continue to guard against. + if Process.euid == 0 && Process.uid == 0 + config.after(:each) do + if Process.uid != 0 + RSpec.configure { |c| c.fail_fast = true } + raise "rspec was invoked as root, but the last test dropped real uid to #{Process.uid}" + end + if Process.euid != 0 + RSpec.configure { |c| c.fail_fast = true } + raise "rspec was invoked as root, but the last test dropped effective uid to #{Process.euid}" + end + end + end + + # raise if anyone commits any test to CI with :focus set on it + if ENV["CI"] + config.before(:example, :focus) do + raise "This example was committed with `:focus` and should not have been" + end + end + + config.before(:suite) do + ARGV.clear + end + + # Protect Rspec from accidental exit(0) causing rspec to terminate without error + config.around(:example) do |ex| + ex.run + rescue SystemExit => e + raise UnexpectedSystemExit.from(e) + + end +end + +require "webrick/utils" +# Webrick uses a centralized/synchronized timeout manager. It works by +# starting a thread to check for timeouts on an interval. The timeout +# checker thread cannot be stopped or canceled in any easy way, and it +# makes calls to Time.new, which fail when rspec is in the process of +# creating a method stub for that method. Since our tests don't rely on +# any timeout behavior enforced by webrick, disable the timeout manager +# via a monkey patch. +# +# Hopefully this fails loudly if the webrick code should change. As of this +# writing, the relevant code is in webrick/utils, which can be located on +# your system with: +# +# $ gem which webrick/utils +module WEBrick + module Utils + class TimeoutHandler + def initialize; end + + def register(*args); end + + def cancel(*args); end + end + end +end + +# Enough stuff needs json serialization that I'm just adding it here for equality asserts +require "chef/json_compat" diff --git a/knife/spec/support/chef_helpers.rb b/knife/spec/support/chef_helpers.rb new file mode 100644 index 0000000000..3738c4a41e --- /dev/null +++ b/knife/spec/support/chef_helpers.rb @@ -0,0 +1,79 @@ +# 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. +# + +CHEF_SPEC_DATA = File.expand_path(__dir__ + "/../data/") +CHEF_SPEC_ASSETS = File.expand_path(__dir__ + "/../functional/assets/") +CHEF_SPEC_BACKUP_PATH = File.join(Dir.tmpdir, "test-backup-path") + +def sha256_checksum(path) + OpenSSL::Digest.hexdigest("SHA256", File.read(path)) +end + +# extracted from Ruby < 2.5 to return a unique temp file name without creating it +def make_tmpname(prefix_suffix, n = nil) + case prefix_suffix + when String + prefix = prefix_suffix + suffix = "" + when Array + prefix = prefix_suffix[0] + suffix = prefix_suffix[1] + else + raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}" + end + t = Time.now.strftime("%Y%m%d") + path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}" + path << "-#{n}" if n + path << suffix +end + +# This is a helper to canonicalize paths that we're using in the file +# tests. +def canonicalize_path(path) + windows? ? path.tr("/", '\\') : path +end + +# Makes a temp directory with a canonical path on any platform. +# Only really needed to work around an issue on Windows where +# Ruby's temp library generates paths with short names. +def make_canonical_temp_directory + temp_directory = Dir.mktmpdir + if windows? + # On Windows, temporary file / directory path names may have shortened + # subdirectory names due to reliance on the TMP and TEMP environment variables + # in some Windows APIs and duplicated logic in Ruby's temp file implementation. + # To work around this in the unit test context, we obtain the long (canonical) + # path name via a Windows system call so that this path name can be used + # in expectations that assume the ability to canonically name paths in comparisons. + # Note that this was not an issue prior to Ruby 2.2 -- with Ruby 2.2, + # some Chef code started to use long file names, while Ruby's temp file implementation + # continued to return the shortened names -- this would cause these particular tests to + # fail if the username happened to be longer than 8 characters. + Chef::ReservedNames::Win32::File.get_long_path_name(temp_directory) + else + temp_directory + end +end + +# Check if a cmd exists on the PATH +def which(cmd) + paths = ENV["PATH"].split(File::PATH_SEPARATOR) + [ "/bin", "/usr/bin", "/sbin", "/usr/sbin" ] + paths.each do |path| + filename = File.join(path, cmd) + return filename if File.executable?(filename) + end + false +end diff --git a/knife/spec/support/key_helpers.rb b/knife/spec/support/key_helpers.rb new file mode 100644 index 0000000000..2a27834050 --- /dev/null +++ b/knife/spec/support/key_helpers.rb @@ -0,0 +1,102 @@ +# +# Author:: Tyler Cloke (<tyler@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. +# + +shared_examples_for "a knife key command" do + let(:stderr) { StringIO.new } + let(:command) do + c = described_class.new([]) + c.ui.config[:disable_editing] = true + allow(c.ui).to receive(:stderr).and_return(stderr) + allow(c.ui).to receive(:stdout).and_return(stderr) + allow(c).to receive(:show_usage) + c + end + + context "before apply_params! is called" do + context "when apply_params! is called with invalid args (missing actor)" do + let(:params) { [] } + it "shows the usage" do + expect(command).to receive(:show_usage) + expect { command.apply_params!(params) }.to exit_with_code(1) + end + + it "outputs the proper error" do + expect { command.apply_params!(params) }.to exit_with_code(1) + expect(stderr.string).to include(command.actor_missing_error) + end + + it "exits 1" do + expect { command.apply_params!(params) }.to exit_with_code(1) + end + end + end # before apply_params! is called + + context "after apply_params! is called with valid args" do + before do + command.apply_params!(params) + end + + it "properly defines the actor" do + expect(command.actor).to eq("charmander") + end + end # after apply_params! is called with valid args + + context "when the command is run" do + before do + allow(command).to receive(:service_object).and_return(service_object) + allow(command).to receive(:name_args).and_return(["charmander"]) + end + + context "when the command is successful" do + before do + expect(service_object).to receive(:run) + end + end + end +end # a knife key command + +shared_examples_for "a knife key command with a keyname as the second arg" do + let(:stderr) { StringIO.new } + let(:command) do + c = described_class.new([]) + c.ui.config[:disable_editing] = true + allow(c.ui).to receive(:stderr).and_return(stderr) + allow(c.ui).to receive(:stdout).and_return(stderr) + allow(c).to receive(:show_usage) + c + end + + context "before apply_params! is called" do + context "when apply_params! is called with invalid args (missing keyname)" do + let(:params) { ["charmander"] } + it "shows the usage" do + expect(command).to receive(:show_usage) + expect { command.apply_params!(params) }.to exit_with_code(1) + end + + it "outputs the proper error" do + expect { command.apply_params!(params) }.to exit_with_code(1) + expect(stderr.string).to include(command.keyname_missing_error) + end + + it "exits 1" do + expect { command.apply_params!(params) }.to exit_with_code(1) + end + end + end # before apply_params! is called +end diff --git a/knife/spec/support/platform_helpers.rb b/knife/spec/support/platform_helpers.rb new file mode 100644 index 0000000000..a3fb95e069 --- /dev/null +++ b/knife/spec/support/platform_helpers.rb @@ -0,0 +1,251 @@ +require "fcntl" +require "chef/mixin/shell_out" +require "ohai/mixin/http_helper" +require "ohai/mixin/gce_metadata" +require "spec/support/chef_helpers" + +class ShellHelpers + extend Chef::Mixin::ShellOut +end + +# magic stolen from bundler/spec/support/less_than_proc.rb +class DependencyProc < Proc + attr_accessor :present + + def self.with(present) + provided = Gem::Version.new(present.dup) + new do |required| + !Gem::Requirement.new(required).satisfied_by?(provided) + end.tap { |l| l.present = present } + end + + def inspect + "\"#{present}\"" + end +end + +def ruby_64bit? + RbConfig::CONFIG["host_cpu"].include?("x86_64") +end + +def ruby_32bit? + RbConfig::CONFIG["host_cpu"].include?("i686") +end + +def windows? + # NOTE this deliberately does not use ChefUtils.windows? because otherwise it would + # pick up the node out of tests, while this tests the hosts running the specs. + !!(RUBY_PLATFORM =~ /mswin|mingw|windows/) +end + +def ohai + # This is defined in spec_helper; it has the `platform` populated. + OHAI_SYSTEM +end + +require "wmi-lite/wmi" if windows? + +def windows_domain_joined? + return false unless windows? + + wmi = WmiLite::Wmi.new + computer_system = wmi.first_of("Win32_ComputerSystem") + computer_system["partofdomain"] +end + +def windows_2012r2? + return false unless windows? + + (win32_os_version && win32_os_version.start_with?("6.3")) +end + +def windows_gte_10? + return false unless windows? + + Gem::Requirement.new(">= 10").satisfied_by?(Gem::Version.new(win32_os_version)) +end + +def win32_os_version + @win32_os_version ||= begin + wmi = WmiLite::Wmi.new + host = wmi.first_of("Win32_OperatingSystem") + host["version"] + end +end + +def windows_powershell_dsc? + return false unless windows? + + supports_dsc = false + begin + wmi = WmiLite::Wmi.new("root/microsoft/windows/desiredstateconfiguration") + lcm = wmi.query("SELECT * FROM meta_class WHERE __this ISA 'MSFT_DSCLocalConfigurationManager'") + supports_dsc = !! lcm + rescue WmiLite::WmiException + end + supports_dsc +end + +def windows_user_right?(right) + return false unless windows? + + require "chef/win32/security" + Chef::ReservedNames::Win32::Security.get_account_right(ENV["USERNAME"]).include?(right) +end + +# detects if the hardware is 64-bit (evaluates to true in "WOW64" mode in a 32-bit app on a 64-bit system) +def windows64? + windows? && ( ENV["PROCESSOR_ARCHITECTURE"] == "AMD64" || ENV["PROCESSOR_ARCHITEW6432"] == "AMD64" ) +end + +# detects if the hardware is 32-bit +def windows32? + windows? && !windows64? +end + +def unix? + !windows? +end + +def linux? + RUBY_PLATFORM.include?("linux") +end + +def macos? + RUBY_PLATFORM.include?("darwin") +end + +def macos_gte_11? + macos? && !!(ohai[:platform_version].to_i >= 11) +end + +def solaris? + RUBY_PLATFORM.include?("solaris") +end + +def freebsd? + RUBY_PLATFORM.include?("freebsd") +end + +def intel_64bit? + !!(ohai[:kernel][:machine] == "x86_64") +end + +def rhel? + !!(ohai[:platform_family] == "rhel") +end + +def rhel6? + rhel? && !!(ohai[:platform_version].to_i == 6) +end + +def opensuse? + suse? && !!(ohai[:platform_version].to_i >= 15) +end + +def rhel7? + rhel? && !!(ohai[:platform_version].to_i == 7) +end + +def rhel8? + rhel? && !!(ohai[:platform_version].to_i == 8) +end + +def rhel_gte_8? + rhel? && !!(ohai[:platform_version].to_i >= 8) +end + +def debian_family? + !!(ohai[:platform_family] == "debian") +end + +def aix? + RUBY_PLATFORM.include?("aix") +end + +def wpar? + !((ohai[:virtualization] || {})[:wpar_no].nil?) +end + +def supports_cloexec? + Fcntl.const_defined?("F_SETFD") && Fcntl.const_defined?("FD_CLOEXEC") +end + +def selinux_enabled? + # This code is currently copied from lib/chef/util/selinux to make + # specs independent of product. + selinuxenabled_path = which("selinuxenabled") + if selinuxenabled_path + cmd = Mixlib::ShellOut.new(selinuxenabled_path, returns: [0, 1]) + cmd_result = cmd.run_command + case cmd_result.exitstatus + when 1 + false + when 0 + true + else + raise "Unknown exit code from command #{selinuxenabled_path}: #{cmd.exitstatus}" + end + else + # We assume selinux is not enabled if selinux utils are not + # installed. + false + end +end + +def suse? + !!(ohai[:platform_family] == "suse") +end + +def root? + return false if windows? + + Process.euid == 0 +end + +def openssl_gte_101? + OpenSSL::OPENSSL_VERSION_NUMBER >= 10001000 +end + +def openssl_lt_101? + !openssl_gte_101? +end + +def aes_256_gcm? + OpenSSL::Cipher.ciphers.include?("aes-256-gcm") +end + +def fips? + ENV["CHEF_FIPS"] == "1" +end + +class HttpHelper + extend Ohai::Mixin::HttpHelper + def self.logger + Chef::Log + end +end + +def gce? + HttpHelper.can_socket_connect?(Ohai::Mixin::GCEMetadata::GCE_METADATA_ADDR, 80) +rescue SocketError + false +end + +def ifconfig? + which("ifconfig") +end + +def choco_installed? + result = ShellHelpers.shell_out("choco --version") + result.stderr.empty? +rescue + false +end + +def pwsh_installed? + result = ShellHelpers.shell_out("pwsh.exe --version") + result.stderr.empty? +rescue + false +end diff --git a/knife/spec/support/platforms/prof/gc.rb b/knife/spec/support/platforms/prof/gc.rb new file mode 100644 index 0000000000..87e50758f3 --- /dev/null +++ b/knife/spec/support/platforms/prof/gc.rb @@ -0,0 +1,51 @@ +# +# Author:: Seth Chisamore (<schisamo@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. +# + +module RSpec + module Prof + module GC + class Profiler + + # GC 1 invokes. + # Index Invoke Time(sec) Use Size(byte) Total Size(byte) Total Object GC time(ms) + # 1 0.012 159240 212940 10647 0.00000000000001530000 + LINE_PATTERN = /^\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)$/.freeze + + def start + ::GC::Profiler.enable unless ::GC::Profiler.enabled? + end + + def stop + ::GC::Profiler.disable + end + + def working_set_size + ::GC.start + ::GC::Profiler.result.scan(LINE_PATTERN)[-1][2].to_i if ::GC::Profiler.enabled? + ensure + ::GC::Profiler.clear + end + + def handle_count + 0 + end + + end + end + end +end diff --git a/knife/spec/support/platforms/prof/win32.rb b/knife/spec/support/platforms/prof/win32.rb new file mode 100644 index 0000000000..da95f16a3d --- /dev/null +++ b/knife/spec/support/platforms/prof/win32.rb @@ -0,0 +1,45 @@ +# +# Author:: Seth Chisamore (<schisamo@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 "chef/win32/process" + +module RSpec + module Prof + module Win32 + class Profiler + + def start + GC.start + end + + def stop + GC.start + end + + def working_set_size + Chef::ReservedNames::Win32::Process.get_current_process.memory_info[:WorkingSetSize] + end + + def handle_count + Chef::ReservedNames::Win32::Process.get_current_process.handle_count + end + end + + end + end +end diff --git a/knife/spec/support/platforms/win32/spec_service.rb b/knife/spec/support/platforms/win32/spec_service.rb new file mode 100644 index 0000000000..27f71a8bf0 --- /dev/null +++ b/knife/spec/support/platforms/win32/spec_service.rb @@ -0,0 +1,57 @@ +# +# Author:: Serdar Sutay (<serdar@lambda.local>) +# 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. +# + +if RUBY_PLATFORM.match?(/mswin|mingw|windows/) + require "win32/daemon" + + class SpecService < ::Win32::Daemon + def service_init + @test_service_file = "#{ENV["TMP"]}/spec_service_file" + end + + def service_main(*startup_parameters) + while running? + unless File.exist?(@test_service_file) + File.open(@test_service_file, "wb") do |f| + f.write("This file is created by SpecService") + end + end + + sleep 1 + end + end + + ################################################################################ + # Control Signal Callback Methods + ################################################################################ + + def service_stop; end + + def service_pause; end + + def service_resume; end + + def service_shutdown; end + end + + # To run this file as a service, it must be called as a script from within + # the Windows Service framework. In that case, kick off the main loop! + if __FILE__ == $0 + SpecService.mainloop + end +end diff --git a/knife/spec/support/recipe_dsl_helper.rb b/knife/spec/support/recipe_dsl_helper.rb new file mode 100644 index 0000000000..2542345ed4 --- /dev/null +++ b/knife/spec/support/recipe_dsl_helper.rb @@ -0,0 +1,83 @@ +# +# This is a helper for functional tests to embed the recipe DSL directly into the rspec example blocks using +# unified mode. +# +# If you wind up wanting to stub/expect on internal details of the resource/provider you are not testing the +# public API and are trying to write a unit test, which this is not designed for. +# +# If you want to start writing full recipes and testing them, doing notifies/subscribes/etc then you are writing +# an integration test, and not a functional single-resource test, which this is not designed for. +# +# Examples: +# +# it "creates a file" do +# FileUtils.rm_f("/tmp/foo.xyz") +# file "/tmp/foo.xyz" do # please use proper tmpdir though +# content "whatever" +# end.should_be_updated +# expect(IO.read("/tmp/foo.xyz").to eql("content") +# end +# +# it "is idempotent" do +# FileUtils.rm_f("/tmp/foo.xyz") +# file "/tmp/foo.xyz" do # please use proper tmpdir though +# content "whatever" +# end.should_be_updated +# file "/tmp/foo.xyz" do # please use proper tmpdir though +# content "whatever" +# end.should_not_be_updated +# expect(IO.read("/tmp/foo.xyz").to eql("content") +# end +# +# it "has a failure" do +# FileUtils.rm_f("/tmp/foo.xyz") +# expect { file "/tmp/lksjdflksjdf/foo.xyz" do +# content "whatever" +# end }.to raise_error(Chef::Exception::EnclosingDirectoryDoesNotExist) +# end +# +module RecipeDSLHelper + include Chef::DSL::Recipe + def event_dispatch + @event_dispatch ||= Chef::EventDispatch::Dispatcher.new + end + + def node + @node ||= Chef::Node.new.tap do |n| + # clone the global ohai data to keep tests fast but reasonably isolated + n.consume_external_attrs(OHAI_SYSTEM.data.dup, {}) + end + end + + def run_context + @run_context ||= Chef::RunContext.new(node, {}, event_dispatch).tap do |rc| + rc.resource_collection.unified_mode = true + Chef::Runner.new(rc) + end + end + + def cookbook_name + "rspec" + end + + def recipe_name + "default" + end + + def declare_resource(type, name, created_at: nil, run_context: self.run_context, &resource_attrs_block) + created_at = caller[0] + rspec_context = self + # we slightly abuse the "enclosing_provider" method_missing magic to send methods to the rspec example block so that + # rspec `let` methods work as arguments to resource properties + resource = super(type, name, created_at: created_at, run_context: run_context, enclosing_provider: rspec_context, &resource_attrs_block) + # we also inject these methods to make terse expression of checking the updated status (so it is more readiable and + # therefore should get used more -- even though it is "should" vs. "expect") + resource.define_singleton_method(:should_be_updated) do + rspec_context.expect(self).to be_updated + end + resource.define_singleton_method(:should_not_be_updated) do + rspec_context.expect(self).not_to be_updated + end + resource + end +end diff --git a/knife/spec/support/shared/context/config.rb b/knife/spec/support/shared/context/config.rb new file mode 100644 index 0000000000..7c4ae8dca6 --- /dev/null +++ b/knife/spec/support/shared/context/config.rb @@ -0,0 +1,18 @@ + +# +# Define config file setups for spec tests here. +# https://www.relishapp.com/rspec/rspec-core/docs/example-groups/shared-context +# + +# Required chef files here: +require "chef/config" + +# Basic config. Nothing fancy. +shared_context "default config options" do + before do + Chef::Config[:cache_path] = windows? ? 'C:\chef' : "/var/chef" + end + + # Don't need to have an after block to reset the config... + # The spec_helper.rb takes care of resetting the config state. +end diff --git a/knife/spec/support/shared/functional/knife.rb b/knife/spec/support/shared/functional/knife.rb new file mode 100644 index 0000000000..89207e76fc --- /dev/null +++ b/knife/spec/support/shared/functional/knife.rb @@ -0,0 +1,37 @@ +# +# Author:: Adam Jacob (<adam@chef.io>) +# Author:: AJ Christensen (<aj@junglist.gen.nz>) +# Author:: Ho-Sheng Hsiao (<hosh@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. +# +module SpecHelpers + module Knife + def redefine_argv(value) + Object.send(:remove_const, :ARGV) + Object.send(:const_set, :ARGV, value) + end + + def with_argv(*argv) + original_argv = ARGV + redefine_argv(argv.flatten) + begin + yield + ensure + redefine_argv(original_argv) + end + end + end +end diff --git a/knife/spec/support/shared/integration/integration_helper.rb b/knife/spec/support/shared/integration/integration_helper.rb new file mode 100644 index 0000000000..c42a04004a --- /dev/null +++ b/knife/spec/support/shared/integration/integration_helper.rb @@ -0,0 +1,122 @@ +# +# Author:: John Keiser (<jkeiser@chef.io>) +# Author:: Ho-Sheng Hsiao (<hosh@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 "tmpdir" +require "fileutils" +require "chef/config" +require "chef/json_compat" +require "chef/server_api" +require "cheffish/rspec/chef_run_support" + +module Cheffish + class BasicChefClient + def_delegators :@run_context, :before_notifications + end +end + +module IntegrationSupport + include ChefZero::RSpec + + def self.included(includer_class) + includer_class.extend(Cheffish::RSpec::ChefRunSupport) + includer_class.extend(ClassMethods) + end + + module ClassMethods + include ChefZero::RSpec + + def when_the_repository(desc, *tags, &block) + context("when the chef repo #{desc}", *tags) do + before :each do + raise "Can only create one directory per test" if @repository_dir + + @repository_dir = Dir.mktmpdir("chef_repo") + Chef::Config.chef_repo_path = @repository_dir + %w{client cookbook data_bag environment node role user}.each do |object_name| + Chef::Config.delete("#{object_name}_path".to_sym) + end + end + + after :each do + if @repository_dir + begin + # TODO: "force" actually means "silence all exceptions". this + # silences a weird permissions error on Windows that we should track + # down, but for now there's no reason for it to blow up our CI. + FileUtils.remove_entry_secure(@repository_dir, force = ChefUtils.windows?) + ensure + @repository_dir = nil + end + end + Dir.chdir(@old_cwd) if @old_cwd + end + + module_eval(&block) + end + end + end + + def api + Chef::ServerAPI.new + end + + def directory(relative_path, &block) + old_parent_path = @parent_path + @parent_path = path_to(relative_path) + FileUtils.mkdir_p(@parent_path) + instance_eval(&block) if block + @parent_path = old_parent_path + end + + def file(relative_path, contents) + filename = path_to(relative_path) + dir = File.dirname(filename) + FileUtils.mkdir_p(dir) unless dir == "." + File.open(filename, "w") do |file| + raw = case contents + when Hash, Array + Chef::JSONCompat.to_json_pretty(contents) + else + contents + end + file.write(raw) + end + end + + def symlink(relative_path, relative_dest) + filename = path_to(relative_path) + dir = File.dirname(filename) + FileUtils.mkdir_p(dir) unless dir == "." + dest_filename = path_to(relative_dest) + File.symlink(dest_filename, filename) + end + + def path_to(relative_path) + File.expand_path(relative_path, (@parent_path || @repository_dir)) + end + + def cb_metadata(name, version, extra_text = "") + "name #{name.inspect}; version #{version.inspect}#{extra_text}" + end + + def cwd(relative_path) + @old_cwd = Dir.pwd + Dir.chdir(path_to(relative_path)) + end +end diff --git a/knife/spec/support/shared/integration/knife_support.rb b/knife/spec/support/shared/integration/knife_support.rb new file mode 100644 index 0000000000..af7b503d16 --- /dev/null +++ b/knife/spec/support/shared/integration/knife_support.rb @@ -0,0 +1,192 @@ +# +# 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 "chef/config" +require "chef/knife" +require "chef/application/knife" +require "logger" +require "chef/log" +require "chef/chef_fs/file_system_cache" + +module KnifeSupport + DEBUG = ENV["DEBUG"] + def knife(*args, input: nil, instance_filter: nil) + # Allow knife('role from file roles/blah.json') rather than requiring the + # arguments to be split like knife('role', 'from', 'file', 'roles/blah.json') + # If any argument will have actual spaces in it, the long form is required. + # (Since knife commands always start with the command name, and command + # names with spaces are always multiple args, this is safe.) + if args.length == 1 + args = args[0].split(/\s+/) + end + + # Make output stable + Chef::Config[:concurrency] = 1 + + # Work on machines where we can't access /var + Dir.mktmpdir("checksums") do |checksums_cache_dir| + Chef::Config[:syntax_check_cache_path] = checksums_cache_dir + + # This is Chef::Knife.run without load_commands--we'll load stuff + # ourselves, thank you very much + stdout = StringIO.new + stderr = StringIO.new + + stdin = if input + StringIO.new(input) + else + STDIN + end + + begin + puts "knife: #{args.join(" ")}" if DEBUG + subcommand_class = Chef::Knife.subcommand_class_from(args) + subcommand_class.options = Chef::Application::Knife.options.merge(subcommand_class.options) + subcommand_class.load_deps + instance = subcommand_class.new(args) + + # Load configs + instance.merge_configs + + # Capture stdout/stderr + instance.ui = Chef::Knife::UI.new(stdout, stderr, stdin, instance.config.merge(disable_editing: true)) + + # Don't print stuff + Chef::Config[:verbosity] = ( DEBUG ? 2 : 0 ) + instance.config[:config_file] = File.join(CHEF_SPEC_DATA, "null_config.rb") + + # Ensure the ChefFS cache is empty + Chef::ChefFS::FileSystemCache.instance.reset! + + # Configure chef with a (mostly) blank knife.rb + # We set a global and then mutate it in our stub knife.rb so we can be + # extra sure that we're not loading someone's real knife.rb and then + # running test scenarios against a real chef server. If things don't + # smell right, abort. + + # To ensure that we don't pick up a user's credentials file we lie through our teeth about + # it's existence. + allow(File).to receive(:file?).and_call_original + allow(File).to receive(:file?).with(File.expand_path("~/.chef/credentials")).and_return(false) + + # Set a canary that is modified by the default null_config.rb config file. + $__KNIFE_INTEGRATION_FAILSAFE_CHECK = "ole" + + # Allow tweaking the knife instance before configuration. + instance_filter.call(instance) if instance_filter + + instance.configure_chef + + # The canary is incorrect, meaning the normal null_config.rb didn't run. Something is wrong. + unless $__KNIFE_INTEGRATION_FAILSAFE_CHECK == "ole ole" + raise Exception, "Potential misconfiguration of integration tests detected. Aborting test." + end + + logger = Logger.new(stderr) + logger.formatter = proc { |severity, datetime, progname, msg| "#{severity}: #{msg}\n" } + Chef::Log.use_log_devices([logger]) + Chef::Log.level = ( DEBUG ? :debug : :warn ) + Chef::Log::Formatter.show_time = false + + instance.run_with_pretty_exceptions(true) + + exit_code = 0 + + # This is how rspec catches exit() + rescue SystemExit => e + exit_code = e.status + ensure + Chef::Config.delete(:syntax_check_cache_path) + Chef::Config.delete(:concurrency) + end + + KnifeResult.new(stdout.string, stderr.string, exit_code) + end + end + + class KnifeResult + + include ::RSpec::Matchers + + def initialize(stdout, stderr, exit_code) + @stdout = stdout + @stderr = stderr + @exit_code = exit_code + end + + attr_reader :stdout + attr_reader :stderr + attr_reader :exit_code + + def should_fail(*args) + expected = {} + args.each do |arg| + if arg.is_a?(Hash) + expected.merge!(arg) + elsif arg.is_a?(Integer) + expected[:exit_code] = arg + else + expected[:stderr] = arg + end + end + expected[:exit_code] = 1 unless expected[:exit_code] + should_result_in(expected) + end + + def should_succeed(*args) + expected = {} + args.each do |arg| + if arg.is_a?(Hash) + expected.merge!(arg) + else + expected[:stdout] = arg + end + end + should_result_in(expected) + end + + private + + def should_result_in(expected) + expected[:stdout] = "" unless expected[:stdout] + expected[:stdout] = expected[:stdout].is_a?(String) ? expected[:stdout].gsub(/[ \t\f\v]+$/, "") : expected[:stdout] + expected[:stderr] = "" unless expected[:stderr] + expected[:stderr] = expected[:stderr].is_a?(String) ? expected[:stderr].gsub(/[ \t\f\v]+$/, "") : expected[:stderr] + expected[:exit_code] = 0 unless expected[:exit_code] + # TODO make this go away + stderr_actual = @stderr.sub(/^WARNING: No knife configuration file found\n/, "") + stderr_actual = stderr_actual.gsub(/[ \t\f\v]+$/, "") + stdout_actual = @stdout + stdout_actual = stdout_actual.gsub(/[ \t\f\v]+$/, "") + if ChefUtils.windows? + stderr_actual = stderr_actual.gsub("\r\n", "\n") + stdout_actual = stdout_actual.gsub("\r\n", "\n") + end + if expected[:stderr].is_a?(Regexp) + expect(stderr_actual).to match(expected[:stderr]) + else + expect(stderr_actual).to eq(expected[:stderr]) + end + expect(@exit_code).to eq(expected[:exit_code]) + if expected[:stdout].is_a?(Regexp) + expect(stdout_actual).to match(expected[:stdout]) + else + expect(stdout_actual).to eq(expected[:stdout]) + end + end + end +end diff --git a/knife/spec/support/shared/matchers/exit_with_code.rb b/knife/spec/support/shared/matchers/exit_with_code.rb new file mode 100644 index 0000000000..a9f1af81ce --- /dev/null +++ b/knife/spec/support/shared/matchers/exit_with_code.rb @@ -0,0 +1,32 @@ +require "rspec/expectations" + +# Lifted from http://stackoverflow.com/questions/1480537/how-can-i-validate-exits-and-aborts-in-rspec +RSpec::Matchers.define :exit_with_code do |exp_code| + actual = nil + match do |block| + begin + block.call + rescue SystemExit => e + actual = e.status + end + actual && actual == exp_code + end + + failure_message do |block| + "expected block to call exit(#{exp_code}) but exit" + + (actual.nil? ? " not called" : "(#{actual}) was called") + end + + failure_message_when_negated do |block| + "expected block not to call exit(#{exp_code})" + end + + description do + "expect block to call exit(#{exp_code})" + end + + def supports_block_expectations? + true + end + +end diff --git a/knife/spec/support/shared/matchers/match_environment_variable.rb b/knife/spec/support/shared/matchers/match_environment_variable.rb new file mode 100644 index 0000000000..393775ea29 --- /dev/null +++ b/knife/spec/support/shared/matchers/match_environment_variable.rb @@ -0,0 +1,17 @@ + +require "rspec/expectations" +require "spec/support/platform_helpers" + +RSpec::Matchers.define :match_environment_variable do |varname| + match do |actual| + expected = if windows? && ENV[varname].nil? + # On Windows, if an environment variable is not set, the command + # `echo %VARNAME%` outputs %VARNAME% + "%#{varname}%" + else + ENV[varname].to_s + end + + actual == expected + end +end diff --git a/knife/spec/support/shared/unit/knife_shared.rb b/knife/spec/support/shared/unit/knife_shared.rb new file mode 100644 index 0000000000..3c7459cfcc --- /dev/null +++ b/knife/spec/support/shared/unit/knife_shared.rb @@ -0,0 +1,39 @@ +# +# Author:: Tyler Cloke (<tyler@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. +# + +shared_examples_for "mandatory field missing" do + context "when field is nil" do + before do + knife.name_args = name_args + end + + it "exits 1" do + expect { knife.run }.to raise_error(SystemExit) + end + + it "prints the usage" do + expect(knife).to receive(:show_usage) + expect { knife.run }.to raise_error(SystemExit) + end + + it "prints a relevant error message" do + expect { knife.run }.to raise_error(SystemExit) + expect(stderr.string).to match(/You must specify a #{fieldname}/) + end + end +end diff --git a/knife/spec/support/shared/unit/mock_shellout.rb b/knife/spec/support/shared/unit/mock_shellout.rb new file mode 100644 index 0000000000..0ea8f64c4d --- /dev/null +++ b/knife/spec/support/shared/unit/mock_shellout.rb @@ -0,0 +1,49 @@ +# +# Author:: John Keiser <jkeiser@chef.io> +# Copyright:: Copyright 2015-2016, John Keiser. +# 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. +# + +# +# Mocks shellout results. Examples: +# mock_shellout_command("systemctl --all", exitstatus: 1) +# +class MockShellout + module RSpec + def mock_shellout_command(command, **result) + allow(::Mixlib::ShellOut).to receive(:new).with(command, anything).and_return MockShellout.new(**result) + end + end + + def initialize(**properties) + @properties = { + stdout: "", + stderr: "", + exitstatus: 0, + }.merge(properties) + end + + def method_missing(name, *args) + @properties[name.to_sym] + end + + def error? + exitstatus != 0 + end + + def error! + raise Mixlib::ShellOut::ShellCommandFailed, "Expected process to exit with 0, but received #{exitstatus}" if error? + end +end diff --git a/knife/spec/tiny_server.rb b/knife/spec/tiny_server.rb new file mode 100644 index 0000000000..786130d0d5 --- /dev/null +++ b/knife/spec/tiny_server.rb @@ -0,0 +1,190 @@ +# +# Author:: Daniel DeLeo (<dan@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 "webrick" +require "webrick/https" +require "rack" +require "singleton" +require "open-uri" +require "chef/config" + +module TinyServer + + class Manager + + # 5 == debug, 3 == warning + LOGGER = WEBrick::Log.new(STDOUT, 3) + DEFAULT_OPTIONS = { + Port: 9000, + Host: "localhost", + Logger: LOGGER, + # SSLEnable: options[:ssl], + # SSLCertName: [ [ 'CN', WEBrick::Utils::getservername ] ], + AccessLog: [], # Remove this option to enable the access log when debugging. + }.freeze + + def initialize(**options) + @options = DEFAULT_OPTIONS.merge(options) + @creator = caller.first + end + + attr_reader :options + attr_reader :creator + attr_reader :server + + def start(timeout = 5) + raise "Server already started!" if server + + # Create the server (but don't start yet) + start_queue = Queue.new + @server = create_server(StartCallback: proc { start_queue << true }) + + @server_thread = Thread.new do + # Ensure any exceptions will cause the main rspec thread to fail too + Thread.current.abort_on_exception = true + server.start + end + + # Wait for the StartCallback to tell us we've started + Timeout.timeout(timeout) do + start_queue.pop + end + end + + def stop(timeout = 5) + if server + server.shutdown + @server = nil + end + + if server_thread + begin + # Wait for a normal shutdown + server_thread.join(timeout) + rescue + # If it wouldn't shut down normally, kill it. + server_thread.kill + server_thread.join(timeout) + end + @server_thread = nil + end + end + + private + + attr_reader :server_thread + + def create_server(**extra_options) + server = WEBrick::HTTPServer.new(**options, **extra_options) + server.mount("/", Rack::Handler::WEBrick, API.instance) + server + end + end + + class API + include Singleton + + GET = "GET".freeze + PUT = "PUT".freeze + POST = "POST".freeze + DELETE = "DELETE".freeze + + attr_reader :routes + + def initialize + clear + end + + def clear + @routes = { GET => [], PUT => [], POST => [], DELETE => [] } + end + + def get(path, response_code, data = nil, headers = nil, &block) + @routes[GET] << Route.new(path, Response.new(response_code, data, headers, &block)) + end + + def put(path, response_code, data = nil, headers = nil, &block) + @routes[PUT] << Route.new(path, Response.new(response_code, data, headers, &block)) + end + + def post(path, response_code, data = nil, headers = nil, &block) + @routes[POST] << Route.new(path, Response.new(response_code, data, headers, &block)) + end + + def delete(path, response_code, data = nil, headers = nil, &block) + @routes[DELETE] << Route.new(path, Response.new(response_code, data, headers, &block)) + end + + def call(env) + if response = response_for_request(env) + response.call + else + debug_info = { message: "no data matches the request for #{env["REQUEST_URI"]}", + available_routes: @routes, request: env } + # Uncomment me for glorious debugging + # pp :not_found => debug_info + [404, { "Content-Type" => "application/json" }, [ Chef::JSONCompat.to_json(debug_info) ]] + end + end + + def response_for_request(env) + if route = @routes[env["REQUEST_METHOD"]].find { |route| route.matches_request?(env["REQUEST_URI"]) } + route.response + end + end + end + + class Route + attr_reader :response + + def initialize(path_spec, response) + @path_spec, @response = path_spec, response + end + + def matches_request?(uri) + uri = URI.parse(uri).request_uri + @path_spec === uri + end + + def to_s + "#{@path_spec} => (#{@response})" + end + + end + + class Response + HEADERS = { "Content-Type" => "application/json" }.freeze + + def initialize(response_code = 200, data = nil, headers = nil, &block) + @response_code, @data = response_code, data + @response_headers = headers ? HEADERS.merge(headers) : HEADERS + @block = block_given? ? block : nil + end + + def call + data = @data || @block.call + [@response_code, @response_headers, Array(data)] + end + + def to_s + "#{@response_code} => #{(@data || @block)}" + end + + end + +end diff --git a/knife/spec/unit/application/knife_spec.rb b/knife/spec/unit/application/knife_spec.rb new file mode 100644 index 0000000000..ad705ab1e5 --- /dev/null +++ b/knife/spec/unit/application/knife_spec.rb @@ -0,0 +1,241 @@ +# +# Author:: AJ Christensen (<aj@junglist.gen.nz>) +# 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 "#{CHEF_SPEC_DATA}/knife_subcommand/test_yourself" + +describe Chef::Application::Knife do + include SpecHelpers::Knife + + before(:all) do + class NoopKnifeCommand < Chef::Knife + option :opt_with_default, + short: "-D VALUE", + long: "-optwithdefault VALUE", + default: "default-value" + + def run; end + end + end + + after(:each) do + # reset some really nasty global state + NoopKnifeCommand.reset_config_loader! + end + + before(:each) do + # Prevent code from getting loaded on every test invocation. + allow(Chef::Knife).to receive(:load_commands) + + @knife = Chef::Application::Knife.new + allow(@knife).to receive(:puts) + allow(@knife).to receive(:trap) + allow(Chef::Knife).to receive(:list_commands) + end + + it "should exit 1 and print the options if no arguments are given at all" do + with_argv([]) do + expect { @knife.run }.to raise_error(SystemExit) { |e| expect(e.status).to eq(1) } + end + end + + it "should exit 2 if run without a sub command" do + with_argv("--user", "adam") do + expect(Chef::Log).to receive(:error).with(/you need to pass a sub\-command/i) + expect { @knife.run }.to raise_error(SystemExit) { |e| expect(e.status).to eq(2) } + end + end + + it "should run a sub command with the applications command line option prototype" do + with_argv(*%w{noop knife command with some args}) do + knife = double(Chef::Knife) + expect(Chef::Knife).to receive(:run).with(ARGV, @knife.options).and_return(knife) + expect(@knife).to receive(:exit).with(0) + @knife.run + end + end + + it "should set the colored output to true by default on windows and true on all other platforms as well" do + with_argv(*%w{noop knife command}) do + expect(@knife).to receive(:exit).with(0) + @knife.run + end + expect(Chef::Config[:color]).to be_truthy + end + + context "validate --format option" do + it "should set the default format summary" do + with_argv(*%w{noop knife command}) do + expect(@knife).to receive(:exit).with(0) + @knife.run + expect(@knife.default_config[:format]).to eq("summary") + end + end + + it "should raise the error for invalid value" do + with_argv(*%w{noop knife command -F abc}) do + expect(STDOUT).to receive(:puts).at_least(2).times + expect { @knife.run }.to raise_error(SystemExit) { |e| expect(e.status).to eq(2) } + end + end + end + + context "when given fips flags" do + context "when Chef::Config[:fips]=false" do + before do + # This is required because the chef-fips pipeline does + # has a default value of true for fips + Chef::Config[:fips] = false + end + + it "does not initialize fips mode when no flags are passed" do + with_argv(*%w{noop knife command}) do + expect(@knife).to receive(:exit).with(0) + expect(Chef::Config).not_to receive(:enable_fips_mode) + @knife.run + expect(Chef::Config[:fips]).to eq(false) + end + end + + it "overwrites the Chef::Config value when passed --fips" do + with_argv(*%w{noop knife command --fips}) do + expect(@knife).to receive(:exit).with(0) + expect(Chef::Config).to receive(:enable_fips_mode) + @knife.run + expect(Chef::Config[:fips]).to eq(true) + end + end + end + + context "when Chef::Config[:fips]=true" do + before do + Chef::Config[:fips] = true + end + + it "initializes fips mode when passed --fips" do + with_argv(*%w{noop knife command --fips}) do + expect(@knife).to receive(:exit).with(0) + expect(Chef::Config).to receive(:enable_fips_mode) + @knife.run + expect(Chef::Config[:fips]).to eq(true) + end + end + + it "overwrites the Chef::Config value when passed --no-fips" do + with_argv(*%w{noop knife command --no-fips}) do + expect(@knife).to receive(:exit).with(0) + expect(Chef::Config).not_to receive(:enable_fips_mode) + @knife.run + expect(Chef::Config[:fips]).to eq(false) + end + end + end + end + + describe "when given a path to the client key" do + it "expands a relative path relative to the CWD" do + relative_path = ".chef/client.pem" + allow(Dir).to receive(:pwd).and_return(CHEF_SPEC_DATA) + with_argv(*%W{noop knife command -k #{relative_path}}) do + expect(@knife).to receive(:exit).with(0) + @knife.run + end + expect(Chef::Config[:client_key]).to eq(File.join(CHEF_SPEC_DATA, relative_path)) + end + + it "expands a ~/home/path to the correct full path" do + home_path = "~/.chef/client.pem" + with_argv(*%W{noop knife command -k #{home_path}}) do + expect(@knife).to receive(:exit).with(0) + @knife.run + end + expect(Chef::Config[:client_key]).to eq(File.join(ENV["HOME"], ".chef/client.pem").gsub((File::ALT_SEPARATOR || '\\'), File::SEPARATOR)) + end + + it "does not expand a full path" do + full_path = if windows? + "C:/chef/client.pem" + else + "/etc/chef/client.pem" + end + with_argv(*%W{noop knife command -k #{full_path}}) do + expect(@knife).to receive(:exit).with(0) + @knife.run + end + expect(Chef::Config[:client_key]).to eq(full_path) + end + end + + describe "with environment configuration" do + before do + Chef::Config[:environment] = nil + end + + it "should default to no environment" do + with_argv(*%w{noop knife command}) do + expect(@knife).to receive(:exit).with(0) + @knife.run + end + expect(Chef::Config[:environment]).to eq(nil) + end + + it "should load the environment from the config file" do + config_file = File.join(CHEF_SPEC_DATA, "environment-config.rb") + with_argv(*%W{noop knife command -c #{config_file}}) do + expect(@knife).to receive(:exit).with(0) + @knife.run + end + expect(Chef::Config[:environment]).to eq("production") + end + + it "should load the environment from the CLI options" do + with_argv(*%w{noop knife command -E development}) do + expect(@knife).to receive(:exit).with(0) + @knife.run + end + expect(Chef::Config[:environment]).to eq("development") + end + + it "should override the config file environment with the CLI environment" do + config_file = File.join(CHEF_SPEC_DATA, "environment-config.rb") + with_argv(*%W{noop knife command -c #{config_file} -E override}) do + expect(@knife).to receive(:exit).with(0) + @knife.run + end + expect(Chef::Config[:environment]).to eq("override") + end + + it "should override the config file environment with the CLI environment regardless of order" do + config_file = File.join(CHEF_SPEC_DATA, "environment-config.rb") + with_argv(*%W{noop knife command -E override -c #{config_file}}) do + expect(@knife).to receive(:exit).with(0) + @knife.run + end + expect(Chef::Config[:environment]).to eq("override") + end + + it "should run a sub command with the applications command line option prototype" do + with_argv(*%w{noop knife command with some args}) do + knife = double(Chef::Knife) + expect(Chef::Knife).to receive(:run).with(ARGV, @knife.options).and_return(knife) + expect(@knife).to receive(:exit).with(0) + @knife.run + end + end + end + +end diff --git a/knife/spec/unit/knife/bootstrap/chef_vault_handler_spec.rb b/knife/spec/unit/knife/bootstrap/chef_vault_handler_spec.rb new file mode 100644 index 0000000000..a4d007611e --- /dev/null +++ b/knife/spec/unit/knife/bootstrap/chef_vault_handler_spec.rb @@ -0,0 +1,152 @@ +# +# Author:: Lamont Granquist <lamont@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" + +describe Chef::Knife::Bootstrap::ChefVaultHandler do + + let(:stdout) { StringIO.new } + let(:stderr) { StringIO.new } + let(:stdin) { StringIO.new } + let(:ui) { Chef::Knife::UI.new(stdout, stderr, stdin, {}) } + + let(:config) { {} } + + let(:client) { Chef::ApiClient.new } + + let(:chef_vault_handler) do + chef_vault_handler = Chef::Knife::Bootstrap::ChefVaultHandler.new(config: config, ui: ui) + chef_vault_handler + end + + context "when there's no vault option" do + it "should report its not doing anything" do + expect(chef_vault_handler.doing_chef_vault?).to be false + end + + it "shouldn't do anything" do + expect(chef_vault_handler).to_not receive(:sanity_check) + expect(chef_vault_handler).to_not receive(:update_bootstrap_vault_json!) + chef_vault_handler + end + end + + context "when setting chef vault items" do + let(:bootstrap_vault_item) { double("ChefVault::Item") } + + before do + expect(chef_vault_handler).to receive(:require_chef_vault!).at_least(:once) + expect(bootstrap_vault_item).to receive(:clients).with(client).at_least(:once) + expect(bootstrap_vault_item).to receive(:save).at_least(:once) + end + + context "from config[:bootstrap_vault_item]" do + it "sets a single item as a scalar" do + config[:bootstrap_vault_item] = { "vault" => "item1" } + expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item) + chef_vault_handler.run(client) + end + + it "sets a single item as an array" do + config[:bootstrap_vault_item] = { "vault" => [ "item1" ] } + expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item) + chef_vault_handler.run(client) + end + + it "sets two items as an array" do + config[:bootstrap_vault_item] = { "vault" => %w{item1 item2} } + expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item) + expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item2").and_return(bootstrap_vault_item) + chef_vault_handler.run(client) + end + + it "sets two vaults from different hash keys" do + config[:bootstrap_vault_item] = { "vault" => %w{item1 item2}, "vault2" => [ "item3" ] } + expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item) + expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item2").and_return(bootstrap_vault_item) + expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault2", "item3").and_return(bootstrap_vault_item) + chef_vault_handler.run(client) + end + end + + context "from config[:bootstrap_vault_json]" do + it "sets a single item as a scalar" do + config[:bootstrap_vault_json] = '{ "vault": "item1" }' + expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item) + chef_vault_handler.run(client) + end + + it "sets a single item as an array" do + config[:bootstrap_vault_json] = '{ "vault": [ "item1" ] }' + expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item) + chef_vault_handler.run(client) + end + + it "sets two items as an array" do + config[:bootstrap_vault_json] = '{ "vault": [ "item1", "item2" ] }' + expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item) + expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item2").and_return(bootstrap_vault_item) + chef_vault_handler.run(client) + end + + it "sets two vaults from different hash keys" do + config[:bootstrap_vault_json] = '{ "vault": [ "item1", "item2" ], "vault2": [ "item3" ] }' + expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item) + expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item2").and_return(bootstrap_vault_item) + expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault2", "item3").and_return(bootstrap_vault_item) + chef_vault_handler.run(client) + end + end + + context "from config[:bootstrap_vault_file]" do + + def setup_file_contents(json) + stringio = StringIO.new(json) + config[:bootstrap_vault_file] = "/foo/bar/baz" + expect(File).to receive(:read).with(config[:bootstrap_vault_file]).and_return(stringio) + end + + it "sets a single item as a scalar" do + setup_file_contents('{ "vault": "item1" }') + expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item) + chef_vault_handler.run(client) + end + + it "sets a single item as an array" do + setup_file_contents('{ "vault": [ "item1" ] }') + expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item) + chef_vault_handler.run(client) + end + + it "sets two items as an array" do + setup_file_contents('{ "vault": [ "item1", "item2" ] }') + expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item) + expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item2").and_return(bootstrap_vault_item) + chef_vault_handler.run(client) + end + + it "sets two vaults from different hash keys" do + setup_file_contents('{ "vault": [ "item1", "item2" ], "vault2": [ "item3" ] }') + expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item) + expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item2").and_return(bootstrap_vault_item) + expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault2", "item3").and_return(bootstrap_vault_item) + chef_vault_handler.run(client) + end + end + end +end diff --git a/knife/spec/unit/knife/bootstrap/client_builder_spec.rb b/knife/spec/unit/knife/bootstrap/client_builder_spec.rb new file mode 100644 index 0000000000..cf6999b093 --- /dev/null +++ b/knife/spec/unit/knife/bootstrap/client_builder_spec.rb @@ -0,0 +1,207 @@ +# +# Author:: Lamont Granquist <lamont@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" + +describe Chef::Knife::Bootstrap::ClientBuilder do + + let(:stdout) { StringIO.new } + let(:stderr) { StringIO.new } + let(:stdin) { StringIO.new } + let(:ui) { Chef::Knife::UI.new(stdout, stderr, stdin, {}) } + + let(:config) { {} } + + let(:chef_config) { {} } + + let(:node_name) { "bevell.wat" } + + let(:rest) { double("Chef::ServerAPI") } + + let(:client_builder) do + client_builder = Chef::Knife::Bootstrap::ClientBuilder.new(config: config, chef_config: chef_config, ui: ui) + allow(client_builder).to receive(:rest).and_return(rest) + allow(client_builder).to receive(:node_name).and_return(node_name) + client_builder + end + + context "#sanity_check!" do + let(:response_404) { OpenStruct.new(code: "404") } + let(:exception_404) { Net::HTTPClientException.new("404 not found", response_404) } + + context "in cases where the prompting fails" do + before do + # should fail early in #run + expect(client_builder).to_not receive(:create_client!) + expect(client_builder).to_not receive(:create_node!) + end + + it "exits when the node exists and the user does not want to delete" do + expect(rest).to receive(:get).with("nodes/#{node_name}") + expect(ui.stdin).to receive(:readline).and_return("n") + expect { client_builder.run }.to raise_error(SystemExit) + end + + it "exits when the client exists and the user does not want to delete" do + expect(rest).to receive(:get).with("nodes/#{node_name}").and_raise(exception_404) + expect(rest).to receive(:get).with("clients/#{node_name}") + expect(ui.stdin).to receive(:readline).and_return("n") + expect { client_builder.run }.to raise_error(SystemExit) + end + end + + context "in cases where the prompting succeeds" do + before do + # mock out the rest of #run + expect(client_builder).to receive(:create_client!) + expect(client_builder).to receive(:create_node!) + end + + it "when both the client and node do not exist it succeeds" do + expect(rest).to receive(:get).with("nodes/#{node_name}").and_raise(exception_404) + expect(rest).to receive(:get).with("clients/#{node_name}").and_raise(exception_404) + expect { client_builder.run }.not_to raise_error + end + + it "when we are allowed to delete an old node" do + expect(rest).to receive(:get).with("nodes/#{node_name}") + expect(ui.stdin).to receive(:readline).and_return("y") + expect(rest).to receive(:get).with("clients/#{node_name}").and_raise(exception_404) + expect(rest).to receive(:delete).with("nodes/#{node_name}") + expect { client_builder.run }.not_to raise_error + end + + it "when we are allowed to delete an old client" do + expect(rest).to receive(:get).with("nodes/#{node_name}").and_raise(exception_404) + expect(rest).to receive(:get).with("clients/#{node_name}") + expect(ui.stdin).to receive(:readline).and_return("y") + expect(rest).to receive(:delete).with("clients/#{node_name}") + expect { client_builder.run }.not_to raise_error + end + + it "when we are are allowed to delete both an old client and node" do + expect(rest).to receive(:get).with("nodes/#{node_name}") + expect(rest).to receive(:get).with("clients/#{node_name}") + expect(ui.stdin).to receive(:readline).twice.and_return("y") + expect(rest).to receive(:delete).with("nodes/#{node_name}") + expect(rest).to receive(:delete).with("clients/#{node_name}") + expect { client_builder.run }.not_to raise_error + end + end + end + + context "#create_client!" do + let(:client) { Chef::ApiClient.new } + + before do + # mock out the rest of #run + expect(client_builder).to receive(:sanity_check) + expect(client_builder).to receive(:create_node!) + end + + it "delegates everything to Chef::ApiClient::Registration and sets client" do + reg_double = double("Chef::ApiClient::Registration") + expect(Chef::ApiClient::Registration).to receive(:new).with(node_name, client_builder.client_path, http_api: rest).and_return(reg_double) + expect(reg_double).to receive(:run).and_return(client) + client_builder.run + expect(client_builder.client).to eq(client) + end + + end + + context "#client_path" do + it "has a public API for the temporary client.pem file" do + expect(client_builder.client_path).to match(/#{node_name}.pem/) + end + end + + context "#create_node!" do + before do + # mock out the rest of #run + expect(client_builder).to receive(:sanity_check) + expect(client_builder).to receive(:create_client!) + # mock out default node building steps + expect(client_builder).to receive(:client_rest).and_return(client_rest) + expect(Chef::Node).to receive(:new).with(chef_server_rest: client_rest).and_return(node) + expect(node).to receive(:name).with(node_name) + expect(node).to receive(:save) + end + + let(:client_rest) { double("Chef::ServerAPI (client)") } + + let(:node) { double("Chef::Node") } + + it "builds a node with a default run_list of []" do + expect(node).to receive(:run_list).with([]) + client_builder.run + end + + it "does not add tags by default" do + allow(node).to receive(:run_list).with([]) + expect(node).to_not receive(:tags) + client_builder.run + end + + it "adds tags to the node when given" do + tag_receiver = [] + + config[:tags] = %w{foo bar} + allow(node).to receive(:run_list).with([]) + allow(node).to receive(:tags).and_return(tag_receiver) + client_builder.run + expect(tag_receiver).to eq %w{foo bar} + end + + it "builds a node when the run_list is a string" do + config[:run_list] = "role[base],role[app]" + expect(node).to receive(:run_list).with(["role[base]", "role[app]"]) + client_builder.run + end + + it "builds a node when the run_list is an Array" do + config[:run_list] = ["role[base]", "role[app]"] + expect(node).to receive(:run_list).with(["role[base]", "role[app]"]) + client_builder.run + end + + it "builds a node with first_boot_attributes if they're given" do + config[:first_boot_attributes] = { baz: :quux } + expect(node).to receive(:normal_attrs=).with({ baz: :quux }) + expect(node).to receive(:run_list).with([]) + client_builder.run + end + + it "builds a node with an environment if its given" do + config[:environment] = "production" + expect(node).to receive(:environment).with("production") + expect(node).to receive(:run_list).with([]) + client_builder.run + end + + it "builds a node with policy_name and policy_group when given" do + config[:policy_name] = "my-app" + config[:policy_group] = "staging" + + expect(node).to receive(:run_list).with([]) + expect(node).to receive(:policy_name=).with("my-app") + expect(node).to receive(:policy_group=).with("staging") + + client_builder.run + end + end +end diff --git a/knife/spec/unit/knife/bootstrap/train_connector_spec.rb b/knife/spec/unit/knife/bootstrap/train_connector_spec.rb new file mode 100644 index 0000000000..0a1091fa8d --- /dev/null +++ b/knife/spec/unit/knife/bootstrap/train_connector_spec.rb @@ -0,0 +1,244 @@ +# +# 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 "ostruct" +require "chef/knife/bootstrap/train_connector" + +describe Chef::Knife::Bootstrap::TrainConnector do + let(:protocol) { "mock" } + let(:family) { "unknown" } + let(:release) { "unknown" } # version + let(:name) { "unknown" } + let(:arch) { "x86_64" } + let(:connection_opts) { {} } # connection opts + let(:host_url) { "mock://user1@example.com" } + let(:mock_connection) { true } + + subject do + # Example groups can still override by setting explicitly it in 'connection_opts' + tc = Chef::Knife::Bootstrap::TrainConnector.new(host_url, protocol, connection_opts) + tc + end + + before(:each) do + if mock_connection + subject.connect! + subject.connection.mock_os( + family: family, + name: name, + release: release, + arch: arch + ) + end + end + + describe "platform helpers" do + context "on linux" do + let(:family) { "debian" } + let(:name) { "ubuntu" } + it "reports that it is linux and unix, because that is how train classifies it" do + expect(subject.unix?).to eq true + expect(subject.linux?).to eq true + expect(subject.windows?).to eq false + end + end + context "on unix" do + let(:family) { "os" } + let(:name) { "mac_os_x" } + it "reports only a unix OS" do + expect(subject.unix?).to eq true + expect(subject.linux?).to eq false + expect(subject.windows?).to eq false + end + end + context "on windows" do + let(:family) { "windows" } + let(:name) { "windows" } + it "reports only a windows OS" do + expect(subject.unix?).to eq false + expect(subject.linux?).to eq false + expect(subject.windows?).to eq true + end + end + end + + describe "#connect!" do + it "establishes the connection to the remote host by waiting for it" do + expect(subject.connection).to receive(:wait_until_ready) + subject.connect! + end + end + + describe "#initialize" do + let(:mock_connection) { false } + + context "when provided target is a proper URL" do + let(:protocol) { "ssh" } + let(:host_url) { "mock://user1@localhost:2200" } + it "correctly configures the instance from the URL" do + expect(subject.config[:backend]).to eq "mock" + expect(subject.config[:port]).to eq 2200 + expect(subject.config[:host]).to eq "localhost" + expect(subject.config[:user]).to eq "user1" + end + + context "and conflicting options are given" do + let(:connection_opts) { { user: "user2", host: "example.com", port: 15 } } + it "resolves them from the URI" do + expect(subject.config[:backend]).to eq "mock" + expect(subject.config[:port]).to eq 2200 + expect(subject.config[:host]).to eq "localhost" + expect(subject.config[:user]).to eq "user1" + end + end + end + + context "when provided target is just a hostname" do + let(:host_url) { "localhost" } + let(:protocol) { "mock" } + it "correctly sets backend protocol from the default" do + expect(subject.config[:backend]).to eq "mock" + end + + context "and options have been provided that are supported by the transport" do + let(:protocol) { "ssh" } + let(:connection_opts) { { port: 15, user: "user2" } } + + it "sets hostname and transport from arguments and provided fields from options" do + expect(subject.config[:backend]).to eq "ssh" + expect(subject.config[:host]).to eq "localhost" + expect(subject.config[:user]).to eq "user2" + expect(subject.config[:port]).to eq 15 + end + + end + + end + + context "when provided target is just a an IP address" do + let(:host_url) { "127.0.0.1" } + let(:protocol) { "mock" } + it "correctly sets backend protocol from the default" do + expect(subject.config[:backend]).to eq "mock" + end + end + end + + describe "#temp_dir" do + context "under windows" do + let(:family) { "windows" } + let(:name) { "windows" } + + it "uses the windows command to create the temp dir" do + expected_command = Chef::Knife::Bootstrap::TrainConnector::MKTEMP_WIN_COMMAND + expect(subject).to receive(:run_command!).with(expected_command) + .and_return double("result", stdout: "C:/a/path") + expect(subject.temp_dir).to eq "C:/a/path" + end + + end + context "under linux and unix-like" do + let(:family) { "debian" } + let(:name) { "ubuntu" } + let(:random) { "wScHX6" } + let(:dir) { "/tmp/chef_#{random}" } + + before do + allow(SecureRandom).to receive(:alphanumeric).with(6).and_return(random) + end + + context "uses the *nix command to create the temp dir and sets ownership to logged-in" do + it "with sudo privilege" do + subject.config[:sudo] = true + expected_command1 = "mkdir -p '#{dir}'" + expected_command2 = "chown user1 '#{dir}'" + expect(subject).to receive(:run_command!).with(expected_command1) + .and_return double("result", stdout: "\r\n") + expect(subject).to receive(:run_command!).with(expected_command2) + .and_return double("result", stdout: "\r\n") + expect(subject.temp_dir).to eq(dir) + end + + it "without sudo privilege" do + expected_command = "mkdir -p '#{dir}'" + expect(subject).to receive(:run_command!).with(expected_command) + .and_return double("result", stdout: "\r\n") + expect(subject.temp_dir).to eq(dir) + end + end + + context "with noise in stderr" do + it "uses the *nix command to create the temp dir" do + expected_command = "mkdir -p '#{dir}'" + expect(subject).to receive(:run_command!).with(expected_command) + .and_return double("result", stdout: "sudo: unable to resolve host hostname.localhost\r\n" + "#{dir}\r\n") + expect(subject.temp_dir).to eq(dir) + end + end + end + end + context "#upload_file_content!" do + it "creates a local file with expected content and uploads it" do + expect(subject).to receive(:upload_file!) do |local_path, remote_path| + expect(File.read(local_path)).to eq "test data" + expect(remote_path).to eq "/target/path" + end + expect_any_instance_of(Tempfile).to receive(:binmode) + subject.upload_file_content!("test data", "/target/path") + end + end + + context "del_file" do + context "on windows" do + let(:family) { "windows" } + let(:name) { "windows" } + it "deletes the file with a windows command" do + expect(subject).to receive(:run_command!) do |cmd, &_handler| + expect(cmd).to match(/Test-Path "deleteme\.txt".*/) + end + subject.del_file!("deleteme.txt") + end + end + context "on unix-like" do + let(:family) { "debian" } + let(:name) { "ubuntu" } + it "deletes the file with a windows command" do + expect(subject).to receive(:run_command!) do |cmd, &_handler| + expect(cmd).to match(/rm -f "deleteme\.txt".*/) + end + subject.del_file!("deleteme.txt") + end + end + end + + context "#run_command!" do + it "raises a RemoteExecutionFailed when the remote execution failed" do + command_result = double("results", stdout: "", stderr: "failed", exit_status: 1) + expect(subject).to receive(:run_command).and_return command_result + + expect { subject.run_command!("test") }.to raise_error do |e| + expect(e.hostname).to eq subject.hostname + expect(e.class).to eq Chef::Knife::Bootstrap::RemoteExecutionFailed + expect(e.stderr).to eq "failed" + expect(e.stdout).to eq "" + expect(e.exit_status).to eq 1 + end + end + end + +end diff --git a/knife/spec/unit/knife/bootstrap_spec.rb b/knife/spec/unit/knife/bootstrap_spec.rb new file mode 100644 index 0000000000..f2f2f48f98 --- /dev/null +++ b/knife/spec/unit/knife/bootstrap_spec.rb @@ -0,0 +1,2220 @@ +# +# Author:: Ian Meyer (<ianmmeyer@gmail.com>) +# Copyright:: Copyright 2010-2016, Ian Meyer +# 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" + +Chef::Knife::Bootstrap.load_deps + +describe Chef::Knife::Bootstrap do + let(:bootstrap_template) { nil } + let(:stderr) { StringIO.new } + let(:bootstrap_cli_options) { [ ] } + let(:linux_test) { true } + let(:windows_test) { false } + let(:linux_test) { false } + let(:unix_test) { false } + let(:ssh_test) { false } + + let(:connection) do + double("TrainConnector", + windows?: windows_test, + linux?: linux_test, + unix?: unix_test) + end + + let(:knife) do + Chef::Log.logger = Logger.new(StringIO.new) + Chef::Config[:knife][:bootstrap_template] = bootstrap_template unless bootstrap_template.nil? + + k = Chef::Knife::Bootstrap.new(bootstrap_cli_options) + allow(k.ui).to receive(:stderr).and_return(stderr) + allow(k).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false) + allow(k).to receive(:connection).and_return connection + k.merge_configs + k + end + + context "#check_license" do + let(:acceptor) { instance_double(LicenseAcceptance::Acceptor) } + + before do + expect(LicenseAcceptance::Acceptor).to receive(:new).and_return(acceptor) + end + + describe "when a license is not required" do + it "does not set the chef_license" do + expect(acceptor).to receive(:license_required?).and_return(false) + knife.check_license + expect(Chef::Config[:chef_license]).to eq(nil) + end + end + + describe "when a license is required" do + it "sets the chef_license" do + expect(acceptor).to receive(:license_required?).and_return(true) + expect(acceptor).to receive(:id_from_mixlib).and_return("id") + expect(acceptor).to receive(:check_and_persist) + expect(acceptor).to receive(:acceptance_value).and_return("accept-no-persist") + knife.check_license + expect(Chef::Config[:chef_license]).to eq("accept-no-persist") + end + end + end + + context "#bootstrap_template" do + it "should default to chef-full" do + expect(knife.bootstrap_template).to be_a_kind_of(String) + expect(File.basename(knife.bootstrap_template)).to eq("chef-full") + end + end + + context "#render_template - when using the chef-full default template" do + let(:rendered_template) do + knife.merge_configs + knife.render_template + end + + it "should render client.rb" do + expect(rendered_template).to match("cat > /etc/chef/client.rb <<'EOP'") + expect(rendered_template).to match("chef_server_url \"https://localhost:443\"") + expect(rendered_template).to match("validation_client_name \"chef-validator\"") + expect(rendered_template).to match("log_location STDOUT") + end + + it "should render first-boot.json" do + expect(rendered_template).to match("cat > /etc/chef/first-boot.json <<'EOP'") + expect(rendered_template).to match('{"run_list":\[\]}') + end + + context "and encrypted_data_bag_secret was provided" do + it "should render encrypted_data_bag_secret file" do + expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true) + expect(knife).to receive(:read_secret).and_return("secrets") + expect(rendered_template).to match("cat > /etc/chef/encrypted_data_bag_secret <<'EOP'") + expect(rendered_template).to match('{"run_list":\[\]}') + expect(rendered_template).to match(/secrets/) + end + end + end + + context "with --bootstrap-vault-item" do + let(:bootstrap_cli_options) { [ "--bootstrap-vault-item", "vault1:item1", "--bootstrap-vault-item", "vault1:item2", "--bootstrap-vault-item", "vault2:item1" ] } + it "sets the knife config cli option correctly" do + expect(knife.config[:bootstrap_vault_item]).to eq({ "vault1" => %w{item1 item2}, "vault2" => ["item1"] }) + end + end + + context "with --bootstrap-preinstall-command" do + command = "while sudo fuser /var/lib/dpkg/lock >/dev/null 2>&1; do\n echo 'waiting for dpkg lock';\n sleep 1;\n done;" + let(:bootstrap_cli_options) { [ "--bootstrap-preinstall-command", command ] } + let(:rendered_template) do + knife.merge_configs + knife.render_template + end + it "configures the preinstall command in the bootstrap template correctly" do + expect(rendered_template).to match(/command/) + end + end + + context "with --bootstrap-proxy" do + let(:bootstrap_cli_options) { [ "--bootstrap-proxy", "1.1.1.1" ] } + let(:rendered_template) do + knife.merge_configs + knife.render_template + end + it "configures the https_proxy environment variable in the bootstrap template correctly" do + expect(rendered_template).to match(/https_proxy="1.1.1.1" export https_proxy/) + end + end + + context "with --bootstrap-no-proxy" do + let(:bootstrap_cli_options) { [ "--bootstrap-no-proxy", "localserver" ] } + let(:rendered_template) do + knife.merge_configs + knife.render_template + end + it "configures the https_proxy environment variable in the bootstrap template correctly" do + expect(rendered_template).to match(/no_proxy="localserver" export no_proxy/) + end + end + + context "with :bootstrap_template and :template_file cli options" do + let(:bootstrap_cli_options) { [ "--bootstrap-template", "my-template", "other-template" ] } + + it "should select bootstrap template" do + expect(File.basename(knife.bootstrap_template)).to eq("my-template") + end + end + + context "when finding templates" do + context "when :bootstrap_template config is set to a file" do + context "that doesn't exist" do + let(:bootstrap_template) { "/opt/blah/not/exists/template.erb" } + + it "raises an error" do + expect { knife.find_template }.to raise_error(Errno::ENOENT) + end + end + + context "that exists" do + let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test.erb")) } + + it "loads the given file as the template" do + expect(Chef::Log).to receive(:trace) + expect(knife.find_template).to eq(File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test.erb"))) + end + end + end + + context "when :bootstrap_template config is set to a template name" do + let(:bootstrap_template) { "example" } + + let(:builtin_template_path) { File.expand_path(File.join(__dir__, "../../../knife/lib/chef/knife/bootstrap/templates", "example.erb")) } + + let(:chef_config_dir_template_path) { "/knife/chef/config/bootstrap/example.erb" } + + let(:env_home_template_path) { "/env/home/.chef/bootstrap/example.erb" } + + let(:gem_files_template_path) { "/Users/schisamo/.rvm/gems/ruby-1.9.2-p180@chef-0.10/gems/knife-windows-0.5.4/lib/chef/knife/bootstrap/fake-bootstrap-template.erb" } + + def configure_chef_config_dir + allow(Chef::Knife).to receive(:chef_config_dir).and_return("/knife/chef/config") + end + + def configure_env_home + allow(Chef::Util::PathHelper).to receive(:home).with(".chef", "bootstrap", "example.erb").and_yield(env_home_template_path) + end + + def configure_gem_files + allow(Gem).to receive(:find_files).and_return([ gem_files_template_path ]) + end + + before(:each) do + expect(File).to receive(:exist?).with(bootstrap_template).and_return(false) + end + + context "when file is available everywhere" do + before do + configure_chef_config_dir + configure_env_home + configure_gem_files + + expect(File).to receive(:exist?).with(builtin_template_path).and_return(true) + end + + it "should load the template from built-in templates" do + expect(knife.find_template).to eq(builtin_template_path) + end + end + + context "when file is available in chef_config_dir" do + before do + configure_chef_config_dir + configure_env_home + configure_gem_files + + expect(File).to receive(:exist?).with(builtin_template_path).and_return(false) + expect(File).to receive(:exist?).with(chef_config_dir_template_path).and_return(true) + + it "should load the template from chef_config_dir" do + knife.find_template.should eq(chef_config_dir_template_path) + end + end + end + + context "when file is available in home directory" do + before do + configure_chef_config_dir + configure_env_home + configure_gem_files + + expect(File).to receive(:exist?).with(builtin_template_path).and_return(false) + expect(File).to receive(:exist?).with(chef_config_dir_template_path).and_return(false) + expect(File).to receive(:exist?).with(env_home_template_path).and_return(true) + end + + it "should load the template from chef_config_dir" do + expect(knife.find_template).to eq(env_home_template_path) + end + end + + context "when file is available in Gem files" do + before do + configure_chef_config_dir + configure_env_home + configure_gem_files + + expect(File).to receive(:exist?).with(builtin_template_path).and_return(false) + expect(File).to receive(:exist?).with(chef_config_dir_template_path).and_return(false) + expect(File).to receive(:exist?).with(env_home_template_path).and_return(false) + expect(File).to receive(:exist?).with(gem_files_template_path).and_return(true) + end + + it "should load the template from Gem files" do + expect(knife.find_template).to eq(gem_files_template_path) + end + end + + context "when file is available in Gem files and home dir doesn't exist" do + before do + configure_chef_config_dir + configure_gem_files + allow(Chef::Util::PathHelper).to receive(:home).with(".chef", "bootstrap", "example.erb").and_return(nil) + + expect(File).to receive(:exist?).with(builtin_template_path).and_return(false) + expect(File).to receive(:exist?).with(chef_config_dir_template_path).and_return(false) + expect(File).to receive(:exist?).with(gem_files_template_path).and_return(true) + end + + it "should load the template from Gem files" do + expect(knife.find_template).to eq(gem_files_template_path) + end + end + end + end + + ["-t", "--bootstrap-template"].each do |t| + context "when #{t} option is given in the command line" do + it "sets the knife :bootstrap_template config" do + knife.parse_options([t, "blahblah"]) + knife.merge_configs + expect(knife.bootstrap_template).to eq("blahblah") + end + end + end + + context "with run_list template" do + let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test.erb")) } + + it "should return an empty run_list" do + expect(knife.render_template).to eq('{"run_list":[]}') + end + + it "should have role[base] in the run_list" do + knife.parse_options(["-r", "role[base]"]) + knife.merge_configs + expect(knife.render_template).to eq('{"run_list":["role[base]"]}') + end + + it "should have role[base] and recipe[cupcakes] in the run_list" do + knife.parse_options(["-r", "role[base],recipe[cupcakes]"]) + knife.merge_configs + expect(knife.render_template).to eq('{"run_list":["role[base]","recipe[cupcakes]"]}') + end + + context "with bootstrap_attribute options" do + let(:jsonfile) do + file = Tempfile.new(["node", ".json"]) + File.open(file.path, "w") { |f| f.puts '{"foo":{"bar":"baz"}}' } + file + end + + it "should have foo => {bar => baz} in the first_boot from cli" do + knife.parse_options(["-j", '{"foo":{"bar":"baz"}}']) + knife.merge_configs + expected_hash = FFI_Yajl::Parser.new.parse('{"foo":{"bar":"baz"},"run_list":[]}') + actual_hash = FFI_Yajl::Parser.new.parse(knife.render_template) + expect(actual_hash).to eq(expected_hash) + end + + it "should have foo => {bar => baz} in the first_boot from file" do + knife.parse_options(["--json-attribute-file", jsonfile.path]) + knife.merge_configs + expected_hash = FFI_Yajl::Parser.new.parse('{"foo":{"bar":"baz"},"run_list":[]}') + actual_hash = FFI_Yajl::Parser.new.parse(knife.render_template) + expect(actual_hash).to eq(expected_hash) + jsonfile.close + end + + it "raises a Chef::Exceptions::BootstrapCommandInputError with the proper error message" do + knife.parse_options(["-j", '{"foo":{"bar":"baz"}}']) + knife.parse_options(["--json-attribute-file", jsonfile.path]) + knife.merge_configs + allow(knife).to receive(:validate_name_args!) + expect(knife).to receive(:check_license) + + expect { knife.run }.to raise_error(Chef::Exceptions::BootstrapCommandInputError) + jsonfile.close + end + end + end + + context "with hints template" do + let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test-hints.erb")) } + + it "should create a hint file when told to" do + knife.parse_options(["--hint", "openstack"]) + knife.merge_configs + expect(knife.render_template).to match(%r{/etc/chef/ohai/hints/openstack.json}) + end + + it "should populate a hint file with JSON when given a file to read" do + allow(::File).to receive(:read).and_return('{ "foo" : "bar" }') + knife.parse_options(["--hint", "openstack=hints/openstack.json"]) + knife.merge_configs + expect(knife.render_template).to match(/\{\"foo\":\"bar\"\}/) + end + end + + describe "specifying no_proxy with various entries" do + subject(:knife) do + k = described_class.new + Chef::Config[:knife][:bootstrap_template] = template_file + allow(k).to receive(:connection).and_return connection + k.parse_options(options) + k.merge_configs + k + end + + let(:options) { ["--bootstrap-no-proxy", setting] } + + let(:template_file) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "no_proxy.erb")) } + + let(:rendered_template) do + knife.render_template + end + + context "via --bootstrap-no-proxy" do + let(:setting) { "api.opscode.com" } + + it "renders the client.rb with a single FQDN no_proxy entry" do + expect(rendered_template).to match(/.*no_proxy\s*"api.opscode.com".*/) + end + end + + context "via --bootstrap-no-proxy multiple" do + let(:setting) { "api.opscode.com,172.16.10.*" } + + it "renders the client.rb with comma-separated FQDN and wildcard IP address no_proxy entries" do + expect(rendered_template).to match(/.*no_proxy\s*"api.opscode.com,172.16.10.\*".*/) + end + end + + context "via --ssl-verify-mode none" do + let(:options) { ["--node-ssl-verify-mode", "none"] } + + it "renders the client.rb with ssl_verify_mode set to :verify_none" do + expect(rendered_template).to match(/ssl_verify_mode :verify_none/) + end + end + + context "via --node-ssl-verify-mode peer" do + let(:options) { ["--node-ssl-verify-mode", "peer"] } + + it "renders the client.rb with ssl_verify_mode set to :verify_peer" do + expect(rendered_template).to match(/ssl_verify_mode :verify_peer/) + end + end + + context "via --node-ssl-verify-mode all" do + let(:options) { ["--node-ssl-verify-mode", "all"] } + + it "raises error" do + expect { rendered_template }.to raise_error(RuntimeError) + end + end + + context "via --node-verify-api-cert" do + let(:options) { ["--node-verify-api-cert"] } + + it "renders the client.rb with verify_api_cert set to true" do + expect(rendered_template).to match(/verify_api_cert true/) + end + end + + context "via --no-node-verify-api-cert" do + let(:options) { ["--no-node-verify-api-cert"] } + + it "renders the client.rb with verify_api_cert set to false" do + expect(rendered_template).to match(/verify_api_cert false/) + end + end + end + + describe "specifying the encrypted data bag secret key" do + let(:secret) { "supersekret" } + let(:options) { [] } + let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "secret.erb")) } + let(:rendered_template) do + knife.parse_options(options) + knife.merge_configs + knife.render_template + end + + it "creates a secret file" do + expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true) + expect(knife).to receive(:read_secret).and_return(secret) + expect(rendered_template).to match(/#{secret}/) + end + + it "renders the client.rb with an encrypted_data_bag_secret entry" do + expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true) + expect(knife).to receive(:read_secret).and_return(secret) + expect(rendered_template).to match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"}) + end + + end + + describe "when transferring trusted certificates" do + let(:rendered_template) do + knife.merge_configs + knife.render_template + end + + before do + Chef::Config[:trusted_certs_dir] = Chef::Util::PathHelper.cleanpath(File.join(CHEF_SPEC_DATA, "trusted_certs")) + end + + it "creates /etc/chef/trusted_certs" do + expect(rendered_template).to match(%r{mkdir -p /etc/chef/trusted_certs}) + end + + it "copies the certificates in the directory" do + certificates = Dir[File.join(Chef::Config[:trusted_certs_dir], "*.{crt,pem}")] + + certificates.each do |cert| + expect(rendered_template).to match(%r{cat > /etc/chef/trusted_certs/#{File.basename(cert)} <<'EOP'}) + end + end + + it "doesn't create /etc/chef/trusted_certs if :trusted_certs_dir is empty" do + Dir.mktmpdir do |dir| + Chef::Config[:trusted_certs_dir] = dir + expect(rendered_template).not_to match(%r{mkdir -p /etc/chef/trusted_certs}) + end + end + end + + context "when doing fips things" do + let(:template_file) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "no_proxy.erb")) } + + before do + Chef::Config[:knife][:bootstrap_template] = template_file + knife.merge_configs + end + + let(:rendered_template) do + knife.render_template + end + + context "when knife is in fips mode" do + before do + Chef::Config[:fips] = true + end + + it "renders 'fips true'" do + expect(rendered_template).to match("fips") + end + end + + context "when knife is not in fips mode" do + before do + # This is required because the chef-fips pipeline does + # has a default value of true for fips + Chef::Config[:fips] = false + end + + it "does not render anything about fips" do + expect(rendered_template).not_to match("fips") + end + end + end + + describe "when transferring client.d" do + + let(:rendered_template) do + knife.merge_configs + knife.render_template + end + + before do + Chef::Config[:client_d_dir] = client_d_dir + end + + context "when client_d_dir is nil" do + let(:client_d_dir) { nil } + + it "does not create /etc/chef/client.d" do + expect(rendered_template).not_to match(%r{mkdir -p /etc/chef/client\.d}) + end + end + + context "when client_d_dir is set" do + let(:client_d_dir) do + Chef::Util::PathHelper.cleanpath( + File.join(__dir__, "../../data/client.d_00") + ) + end + + it "creates /etc/chef/client.d" do + expect(rendered_template).to match("mkdir -p /etc/chef/client\.d") + end + + context "a flat directory structure" do + it "escapes single-quotes" do + expect(rendered_template).to match("cat > /etc/chef/client.d/02-strings.rb <<'EOP'") + expect(rendered_template).to match("something '\\\\''/foo/bar'\\\\''") + end + + it "creates a file 00-foo.rb" do + expect(rendered_template).to match("cat > /etc/chef/client.d/00-foo.rb <<'EOP'") + expect(rendered_template).to match("d6f9b976-289c-4149-baf7-81e6ffecf228") + end + it "creates a file bar" do + expect(rendered_template).to match("cat > /etc/chef/client.d/bar <<'EOP'") + expect(rendered_template).to match("1 / 0") + end + end + + context "a nested directory structure" do + let(:client_d_dir) do + Chef::Util::PathHelper.cleanpath( + File.join(__dir__, "../../data/client.d_01") + ) + end + it "creates a file foo/bar.rb" do + expect(rendered_template).to match("cat > /etc/chef/client.d/foo/bar.rb <<'EOP'") + expect(rendered_template).to match("1 / 0") + end + end + end + end + + describe "#connection_protocol" do + let(:host_descriptor) { "example.com" } + let(:config) { {} } + let(:knife_connection_protocol) { nil } + before do + allow(knife).to receive(:config).and_return config + allow(knife).to receive(:host_descriptor).and_return host_descriptor + if knife_connection_protocol + Chef::Config[:knife][:connection_protocol] = knife_connection_protocol + knife.merge_configs + end + end + + context "when protocol is part of the host argument" do + let(:host_descriptor) { "winrm://myhost" } + + it "returns the value provided by the host argument" do + expect(knife.connection_protocol).to eq "winrm" + end + end + + context "when protocol is provided via the CLI flag" do + let(:config) { { connection_protocol: "winrm" } } + it "returns that value" do + expect(knife.connection_protocol).to eq "winrm" + end + + end + context "when protocol is provided via the host argument and the CLI flag" do + let(:host_descriptor) { "ssh://example.com" } + let(:config) { { connection_protocol: "winrm" } } + + it "returns the value provided by the host argument" do + expect(knife.connection_protocol).to eq "ssh" + end + end + + context "when no explicit protocol is provided" do + let(:config) { {} } + let(:host_descriptor) { "example.com" } + let(:knife_connection_protocol) { "winrm" } + it "falls back to knife config" do + expect(knife.connection_protocol).to eq "winrm" + end + context "and there is no knife bootstrap_protocol" do + let(:knife_connection_protocol) { nil } + it "falls back to 'ssh'" do + expect(knife.connection_protocol).to eq "ssh" + end + end + end + + end + + describe "#validate_protocol!" do + let(:host_descriptor) { "example.com" } + let(:config) { {} } + let(:connection_protocol) { "ssh" } + before do + allow(knife).to receive(:config).and_return config + allow(knife).to receive(:connection_protocol).and_return connection_protocol + allow(knife).to receive(:host_descriptor).and_return host_descriptor + end + + context "when protocol is provided both in the URL and via --protocol" do + + context "and they do not match" do + let(:connection_protocol) { "ssh" } + let(:config) { { connection_protocol: "winrm" } } + it "outputs an error and exits" do + expect(knife.ui).to receive(:error) + expect { knife.validate_protocol! }.to raise_error SystemExit + end + end + + context "and they do match" do + let(:connection_protocol) { "winrm" } + let(:config) { { connection_protocol: "winrm" } } + it "returns true" do + expect(knife.validate_protocol!).to eq true + end + end + end + + context "and the protocol is supported" do + + Chef::Knife::Bootstrap::SUPPORTED_CONNECTION_PROTOCOLS.each do |proto| + let(:connection_protocol) { proto } + it "returns true for #{proto}" do + expect(knife.validate_protocol!).to eq true + end + end + end + + context "and the protocol is not supported" do + let(:connection_protocol) { "invalid" } + it "outputs an error and exits" do + expect(knife.ui).to receive(:error).with(/Unsupported protocol '#{connection_protocol}'/) + expect { knife.validate_protocol! }.to raise_error SystemExit + end + end + end + + describe "#validate_policy_options!" do + + context "when only policy_name is given" do + + let(:bootstrap_cli_options) { %w{ --policy-name my-app-server } } + + it "returns an error stating that policy_name and policy_group must be given together" do + expect { knife.validate_policy_options! }.to raise_error(SystemExit) + expect(stderr.string).to include("ERROR: --policy-name and --policy-group must be specified together") + end + + end + + context "when only policy_group is given" do + + let(:bootstrap_cli_options) { %w{ --policy-group staging } } + + it "returns an error stating that policy_name and policy_group must be given together" do + expect { knife.validate_policy_options! }.to raise_error(SystemExit) + expect(stderr.string).to include("ERROR: --policy-name and --policy-group must be specified together") + end + + end + + context "when both policy_name and policy_group are given, but run list is also given" do + + let(:bootstrap_cli_options) { %w{ --policy-name my-app --policy-group staging --run-list cookbook } } + + it "returns an error stating that policyfile and run_list are exclusive" do + expect { knife.validate_policy_options! }.to raise_error(SystemExit) + expect(stderr.string).to include("ERROR: Policyfile options and --run-list are exclusive") + end + + end + + context "when policy_name and policy_group are given with no conflicting options" do + + let(:bootstrap_cli_options) { %w{ --policy-name my-app --policy-group staging } } + + it "passes options validation" do + expect { knife.validate_policy_options! }.to_not raise_error + end + + it "passes them into the bootstrap context" do + expect(knife.bootstrap_context.first_boot).to have_key(:policy_name) + expect(knife.bootstrap_context.first_boot).to have_key(:policy_group) + end + + it "ensures that run_list is not set in the bootstrap context" do + expect(knife.bootstrap_context.first_boot).to_not have_key(:run_list) + end + + end + + # https://github.com/chef/chef/issues/4131 + # Arguably a bug in the plugin: it shouldn't be setting this to nil, but it + # worked before, so make it work now. + context "when a plugin sets the run list option to nil" do + before do + knife.config[:run_list] = nil + end + + it "passes options validation" do + expect { knife.validate_policy_options! }.to_not raise_error + end + end + end + + # TODO - this is the only cli option we validate the _option_ itself - + # so we'll know if someone accidentally deletes or renames use_sudo_password + # Is this worht keeping? If so, then it seems we should expand it + # to cover all options. + context "validating use_sudo_password option" do + it "use_sudo_password contains description and long params for help" do + expect(knife.options).to(have_key(:use_sudo_password)) \ + && expect(knife.options[:use_sudo_password][:description].to_s).not_to(eq(""))\ + && expect(knife.options[:use_sudo_password][:long].to_s).not_to(eq("")) + end + end + + context "#connection_opts" do + let(:connection_protocol) { "ssh" } + before do + allow(knife).to receive(:connection_protocol).and_return connection_protocol + end + context "behavioral test: " do + let(:expected_connection_opts) do + { base_opts: true, + ssh_identity_opts: true, + ssh_opts: true, + gateway_opts: true, + host_verify_opts: true, + sudo_opts: true, + winrm_opts: true } + end + + it "queries and merges only expected configurations" do + expect(knife).to receive(:base_opts).and_return({ base_opts: true }) + expect(knife).to receive(:host_verify_opts).and_return({ host_verify_opts: true }) + expect(knife).to receive(:gateway_opts).and_return({ gateway_opts: true }) + expect(knife).to receive(:sudo_opts).and_return({ sudo_opts: true }) + expect(knife).to receive(:winrm_opts).and_return({ winrm_opts: true }) + expect(knife).to receive(:ssh_opts).and_return({ ssh_opts: true }) + expect(knife).to receive(:ssh_identity_opts).and_return({ ssh_identity_opts: true }) + expect(knife.connection_opts).to match expected_connection_opts + end + end + + context "functional test: " do + context "when protocol is winrm" do + let(:connection_protocol) { "winrm" } + # context "and neither CLI nor Chef::Config config entries have been provided" + # end + context "and all supported values are provided as Chef::Config entries" do + before do + # Set everything to easily identifiable and obviously fake values + # to verify that Chef::Config is being sourced instead of knife.config + knife.config = {} + Chef::Config[:knife][:max_wait] = 9999 + Chef::Config[:knife][:winrm_user] = "winbob" + Chef::Config[:knife][:winrm_port] = 9999 + Chef::Config[:knife][:ca_trust_file] = "trust.me" + Chef::Config[:knife][:kerberos_realm] = "realm" + Chef::Config[:knife][:kerberos_service] = "service" + Chef::Config[:knife][:winrm_auth_method] = "kerberos" # default is negotiate + Chef::Config[:knife][:winrm_basic_auth_only] = true + Chef::Config[:knife][:winrm_no_verify_cert] = true + Chef::Config[:knife][:session_timeout] = 9999 + Chef::Config[:knife][:winrm_ssl] = true + Chef::Config[:knife][:winrm_ssl_peer_fingerprint] = "ABCDEF" + end + + context "and no CLI options have been given" do + let(:expected_result) do + { + logger: Chef::Log, # not configurable + ca_trust_path: "trust.me", + max_wait_until_ready: 9999, + operation_timeout: 9999, + ssl_peer_fingerprint: "ABCDEF", + winrm_transport: "kerberos", + winrm_basic_auth_only: true, + user: "winbob", + port: 9999, + self_signed: true, + ssl: true, + kerberos_realm: "realm", + kerberos_service: "service", + } + end + + it "generates a config hash using the Chef::Config values" do + knife.merge_configs + expect(knife.connection_opts).to match expected_result + end + + end + + context "and some CLI options have been given" do + let(:expected_result) do + { + logger: Chef::Log, # not configurable + ca_trust_path: "no trust", + max_wait_until_ready: 9999, + operation_timeout: 9999, + ssl_peer_fingerprint: "ABCDEF", + winrm_transport: "kerberos", + winrm_basic_auth_only: true, + user: "microsoftbob", + port: 12, + self_signed: true, + ssl: true, + kerberos_realm: "realm", + kerberos_service: "service", + password: "lobster", + } + end + + before do + knife.config[:ca_trust_file] = "no trust" + knife.config[:connection_user] = "microsoftbob" + knife.config[:connection_port] = 12 + knife.config[:winrm_port] = "13" # indirectly verify we're not looking for the wrong CLI flag + knife.config[:connection_password] = "lobster" + end + + it "generates a config hash using the CLI options when available and falling back to Chef::Config values" do + knife.merge_configs + expect(knife.connection_opts).to match expected_result + end + end + + context "and all CLI options have been given" do + before do + # We'll force kerberos vi knife.config because it + # causes additional options to populate - make sure + # Chef::Config is different so we can be sure that we didn't + # pull in the Chef::Config value + Chef::Config[:knife][:winrm_auth_method] = "negotiate" + knife.config[:connection_password] = "blue" + knife.config[:max_wait] = 1000 + knife.config[:connection_user] = "clippy" + knife.config[:connection_port] = 1000 + knife.config[:winrm_port] = 1001 # We should not see this value get used + + knife.config[:ca_trust_file] = "trust.the.internet" + knife.config[:kerberos_realm] = "otherrealm" + knife.config[:kerberos_service] = "otherservice" + knife.config[:winrm_auth_method] = "kerberos" # default is negotiate + knife.config[:winrm_basic_auth_only] = false + knife.config[:winrm_no_verify_cert] = false + knife.config[:session_timeout] = 1000 + knife.config[:winrm_ssl] = false + knife.config[:winrm_ssl_peer_fingerprint] = "FEDCBA" + end + let(:expected_result) do + { + logger: Chef::Log, # not configurable + ca_trust_path: "trust.the.internet", + max_wait_until_ready: 1000, + operation_timeout: 1000, + ssl_peer_fingerprint: "FEDCBA", + winrm_transport: "kerberos", + winrm_basic_auth_only: false, + user: "clippy", + port: 1000, + self_signed: false, + ssl: false, + kerberos_realm: "otherrealm", + kerberos_service: "otherservice", + password: "blue", + } + end + it "generates a config hash using the CLI options and pulling nothing from Chef::Config" do + knife.merge_configs + expect(knife.connection_opts).to match expected_result + end + end + end # with underlying Chef::Config values + + context "and no values are provided from Chef::Config or CLI" do + before do + # We will use knife's actual config since these tests + # have assumptions based on CLI default values + end + let(:expected_result) do + { + logger: Chef::Log, + operation_timeout: 60, + self_signed: false, + ssl: false, + ssl_peer_fingerprint: nil, + winrm_basic_auth_only: false, + winrm_transport: "negotiate", + } + end + it "populates appropriate defaults" do + knife.merge_configs + expect(knife.connection_opts).to match expected_result + end + end + end # winrm + + context "when protocol is ssh" do + let(:connection_protocol) { "ssh" } + # context "and neither CLI nor Chef::Config config entries have been provided" + # end + context "and all supported values are provided as Chef::Config entries" do + before do + # Set everything to easily identifiable and obviously fake values + # to verify that Chef::Config is being sourced instead of knife.config + knife.config = {} + Chef::Config[:knife][:max_wait] = 9999 + Chef::Config[:knife][:session_timeout] = 9999 + Chef::Config[:knife][:ssh_user] = "sshbob" + Chef::Config[:knife][:ssh_port] = 9999 + Chef::Config[:knife][:host_key_verify] = false + Chef::Config[:knife][:ssh_gateway_identity] = "/gateway.pem" + Chef::Config[:knife][:ssh_gateway] = "admin@mygateway.local:1234" + Chef::Config[:knife][:ssh_identity_file] = "/identity.pem" + Chef::Config[:knife][:use_sudo_password] = false # We have no password. + end + + context "and no CLI options have been given" do + let(:expected_result) do + { + logger: Chef::Log, # not configurable + max_wait_until_ready: 9999.0, + connection_timeout: 9999, + user: "sshbob", + bastion_host: "mygateway.local", + bastion_port: 1234, + bastion_user: "admin", + forward_agent: false, + keys_only: true, + key_files: ["/identity.pem", "/gateway.pem"], + sudo: false, + verify_host_key: "always", + port: 9999, + non_interactive: true, + } + end + + it "generates a correct config hash using the Chef::Config values" do + knife.merge_configs + expect(knife.connection_opts).to match expected_result + end + end + + context "and unsupported Chef::Config options are given in Chef::Config, not in CLI" do + before do + Chef::Config[:knife][:password] = "blah" + Chef::Config[:knife][:ssh_password] = "blah" + Chef::Config[:knife][:preserve_home] = true + Chef::Config[:knife][:use_sudo] = true + Chef::Config[:knife][:ssh_forward_agent] = "blah" + end + it "does not include the corresponding option in the connection options" do + knife.merge_configs + expect(knife.connection_opts.key?(:password)).to eq false + expect(knife.connection_opts.key?(:ssh_forward_agent)).to eq false + expect(knife.connection_opts.key?(:use_sudo)).to eq false + expect(knife.connection_opts.key?(:preserve_home)).to eq false + end + end + + context "and some CLI options have been given" do + before do + knife.config = {} + knife.config[:connection_user] = "sshalice" + knife.config[:connection_port] = 12 + knife.config[:ssh_port] = "13" # canary to indirectly verify we're not looking for the wrong CLI flag + knife.config[:connection_password] = "feta cheese" + knife.config[:max_wait] = 150 + knife.config[:session_timeout] = 120 + knife.config[:use_sudo] = true + knife.config[:use_sudo_pasword] = true + knife.config[:ssh_forward_agent] = true + end + + let(:expected_result) do + { + logger: Chef::Log, # not configurable + max_wait_until_ready: 150.0, # cli + connection_timeout: 120, # cli + user: "sshalice", # cli + password: "feta cheese", # cli + bastion_host: "mygateway.local", # Config + bastion_port: 1234, # Config + bastion_user: "admin", # Config + forward_agent: true, # cli + keys_only: false, # implied false from config password present + key_files: ["/identity.pem", "/gateway.pem"], # Config + sudo: true, # ccli + verify_host_key: "always", # Config + port: 12, # cli + non_interactive: true, + } + end + + it "generates a config hash using the CLI options when available and falling back to Chef::Config values" do + knife.merge_configs + expect(knife.connection_opts).to match expected_result + end + end + + context "and all CLI options have been given" do + before do + knife.config = {} + knife.config[:max_wait] = 150 + knife.config[:session_timeout] = 120 + knife.config[:connection_user] = "sshroot" + knife.config[:connection_port] = 1000 + knife.config[:connection_password] = "blah" + knife.config[:forward_agent] = true + knife.config[:use_sudo] = true + knife.config[:use_sudo_password] = true + knife.config[:preserve_home] = true + knife.config[:use_sudo_pasword] = true + knife.config[:ssh_forward_agent] = true + knife.config[:ssh_verify_host_key] = true + knife.config[:ssh_gateway_identity] = "/gateway-identity.pem" + knife.config[:ssh_gateway] = "me@example.com:10" + knife.config[:ssh_identity_file] = "/my-identity.pem" + + # We'll set these as canaries - if one of these values shows up + # in a failed test, then the behavior of not pulling from these keys + # out of knife.config is broken: + knife.config[:ssh_user] = "do not use" + knife.config[:ssh_port] = 1001 + end + let(:expected_result) do + { + logger: Chef::Log, # not configurable + max_wait_until_ready: 150, + connection_timeout: 120, + user: "sshroot", + password: "blah", + port: 1000, + bastion_host: "example.com", + bastion_port: 10, + bastion_user: "me", + forward_agent: true, + keys_only: false, + key_files: ["/my-identity.pem", "/gateway-identity.pem"], + sudo: true, + sudo_options: "-H", + sudo_password: "blah", + verify_host_key: true, + non_interactive: true, + } + end + it "generates a config hash using the CLI options and pulling nothing from Chef::Config" do + knife.merge_configs + expect(knife.connection_opts).to match expected_result + end + end + end + context "and no values are provided from Chef::Config or CLI" do + before do + # We will use knife's actual config since these tests + # have assumptions based on CLI default values + config = {} + end + + let(:expected_result) do + { + forward_agent: false, + key_files: [], + logger: Chef::Log, + keys_only: false, + sudo: false, + verify_host_key: "always", + non_interactive: true, + connection_timeout: 60, + } + end + it "populates appropriate defaults" do + knife.merge_configs + expect(knife.connection_opts).to match expected_result + end + end + + end # ssh + end # functional tests + + end # connection_opts + + context "#base_opts" do + let(:connection_protocol) { nil } + + before do + allow(knife).to receive(:connection_protocol).and_return connection_protocol + end + + context "for all protocols" do + context "when password is provided" do + before do + knife.config[:connection_port] = 250 + knife.config[:connection_user] = "test" + knife.config[:connection_password] = "opscode" + end + + let(:expected_opts) do + { + port: 250, + user: "test", + logger: Chef::Log, + password: "opscode", + } + end + it "generates the correct options" do + expect(knife.base_opts).to eq expected_opts + end + + end + + context "when password is not provided" do + before do + knife.config[:connection_port] = 250 + knife.config[:connection_user] = "test" + end + + let(:expected_opts) do + { + port: 250, + user: "test", + logger: Chef::Log, + } + end + it "generates the correct options" do + expect(knife.base_opts).to eq expected_opts + end + end + end + end + + context "#host_verify_opts" do + let(:connection_protocol) { nil } + before do + allow(knife).to receive(:connection_protocol).and_return connection_protocol + end + + context "for winrm" do + let(:connection_protocol) { "winrm" } + it "returns the expected configuration" do + knife.config[:winrm_no_verify_cert] = true + expect(knife.host_verify_opts).to eq( { self_signed: true } ) + end + it "provides a correct default when no option given" do + expect(knife.host_verify_opts).to eq( { self_signed: false } ) + end + end + + context "for ssh" do + let(:connection_protocol) { "ssh" } + it "returns the expected configuration" do + knife.config[:ssh_verify_host_key] = false + expect(knife.host_verify_opts).to eq( { verify_host_key: false } ) + end + it "provides a correct default when no option given" do + expect(knife.host_verify_opts).to eq( { verify_host_key: "always" } ) + end + end + end + + # TODO - test keys_only, password, config source behavior + context "#ssh_identity_opts" do + let(:connection_protocol) { nil } + before do + allow(knife).to receive(:connection_protocol).and_return connection_protocol + end + + context "for winrm" do + let(:connection_protocol) { "winrm" } + it "returns an empty hash" do + expect(knife.ssh_identity_opts).to eq({}) + end + end + + context "for ssh" do + let(:connection_protocol) { "ssh" } + context "when an identity file is specified" do + before do + knife.config[:ssh_identity_file] = "/identity.pem" + end + it "generates the expected configuration" do + expect(knife.ssh_identity_opts).to eq({ + key_files: [ "/identity.pem" ], + keys_only: true, + }) + end + context "and a password is also specified" do + before do + knife.config[:connection_password] = "blah" + end + it "generates the expected configuration (key, keys_only false)" do + expect(knife.ssh_identity_opts).to eq({ + key_files: [ "/identity.pem" ], + keys_only: false, + }) + end + end + + context "and a gateway is not specified" do + context "but a gateway identity file is specified" do + it "does not include the gateway identity file in keys" do + expect(knife.ssh_identity_opts).to eq({ + key_files: ["/identity.pem"], + keys_only: true, + }) + end + + end + + end + + context "and a gatway is specified" do + before do + knife.config[:ssh_gateway] = "example.com" + end + context "and a gateway identity file is not specified" do + it "config includes only identity file and not gateway identity" do + expect(knife.ssh_identity_opts).to eq({ + key_files: [ "/identity.pem" ], + keys_only: true, + }) + end + end + + context "and a gateway identity file is also specified" do + before do + knife.config[:ssh_gateway_identity] = "/gateway.pem" + end + + it "generates the expected configuration (both keys, keys_only true)" do + expect(knife.ssh_identity_opts).to eq({ + key_files: [ "/identity.pem", "/gateway.pem" ], + keys_only: true, + }) + end + end + end + end + + context "when no identity file is specified" do + it "generates the expected configuration (no keys, keys_only false)" do + expect(knife.ssh_identity_opts).to eq( { + key_files: [ ], + keys_only: false, + }) + end + context "and a gateway with gateway identity file is specified" do + before do + knife.config[:ssh_gateway] = "host" + knife.config[:ssh_gateway_identity] = "/gateway.pem" + end + + it "generates the expected configuration (gateway key, keys_only false)" do + expect(knife.ssh_identity_opts).to eq({ + key_files: [ "/gateway.pem" ], + keys_only: false, + }) + end + end + end + end + end + + context "#gateway_opts" do + let(:connection_protocol) { nil } + before do + allow(knife).to receive(:connection_protocol).and_return connection_protocol + end + + context "for winrm" do + let(:connection_protocol) { "winrm" } + it "returns an empty hash" do + expect(knife.gateway_opts).to eq({}) + end + end + + context "for ssh" do + let(:connection_protocol) { "ssh" } + context "and ssh_gateway with hostname, user and port provided" do + before do + knife.config[:ssh_gateway] = "testuser@gateway:9021" + end + it "returns a proper bastion host config subset" do + expect(knife.gateway_opts).to eq({ + bastion_user: "testuser", + bastion_host: "gateway", + bastion_port: 9021, + }) + end + end + context "and ssh_gateway with only hostname is given" do + before do + knife.config[:ssh_gateway] = "gateway" + end + it "returns a proper bastion host config subset" do + expect(knife.gateway_opts).to eq({ + bastion_user: nil, + bastion_host: "gateway", + bastion_port: nil, + }) + end + end + context "and ssh_gateway with hostname and user is is given" do + before do + knife.config[:ssh_gateway] = "testuser@gateway" + end + it "returns a proper bastion host config subset" do + expect(knife.gateway_opts).to eq({ + bastion_user: "testuser", + bastion_host: "gateway", + bastion_port: nil, + }) + end + end + + context "and ssh_gateway with hostname and port is is given" do + before do + knife.config[:ssh_gateway] = "gateway:11234" + end + it "returns a proper bastion host config subset" do + expect(knife.gateway_opts).to eq({ + bastion_user: nil, + bastion_host: "gateway", + bastion_port: 11234, + }) + end + end + + context "and ssh_gateway is not provided" do + it "returns an empty hash" do + expect(knife.gateway_opts).to eq({}) + end + end + end + end + + context "#sudo_opts" do + let(:connection_protocol) { nil } + before do + allow(knife).to receive(:connection_protocol).and_return connection_protocol + end + + context "for winrm" do + let(:connection_protocol) { "winrm" } + it "returns an empty hash" do + expect(knife.sudo_opts).to eq({}) + end + end + + context "for ssh" do + let(:connection_protocol) { "ssh" } + context "when use_sudo is set" do + before do + knife.config[:use_sudo] = true + end + + it "returns a config that enables sudo" do + expect(knife.sudo_opts).to eq( { sudo: true } ) + end + + context "when use_sudo_password is also set" do + before do + knife.config[:use_sudo_password] = true + knife.config[:connection_password] = "opscode" + end + it "includes :connection_password value in a sudo-enabled configuration" do + expect(knife.sudo_opts).to eq({ + sudo: true, + sudo_password: "opscode", + }) + end + end + + context "when preserve_home is set" do + before do + knife.config[:preserve_home] = true + end + it "enables sudo with sudo_option to preserve home" do + expect(knife.sudo_opts).to eq({ + sudo_options: "-H", + sudo: true, + }) + end + end + end + + context "when use_sudo is not set" do + before do + knife.config[:use_sudo_password] = true + knife.config[:preserve_home] = true + end + it "returns configuration for sudo off, ignoring other related options" do + expect(knife.sudo_opts).to eq( { sudo: false } ) + end + end + end + end + + context "#ssh_opts" do + let(:connection_protocol) { nil } + before do + allow(knife).to receive(:connection_protocol).and_return connection_protocol + end + + context "for ssh" do + let(:connection_protocol) { "ssh" } + let(:default_opts) do + { + non_interactive: true, + forward_agent: false, + connection_timeout: 60, + } + end + + context "by default" do + it "returns a configuration hash with appropriate defaults" do + expect(knife.ssh_opts).to eq default_opts + end + end + + context "when ssh_forward_agent has a value" do + before do + knife.config[:ssh_forward_agent] = true + end + it "returns a default configuration hash with forward_agent set to true" do + expect(knife.ssh_opts).to eq(default_opts.merge(forward_agent: true)) + end + end + context "when session_timeout has a value" do + before do + knife.config[:session_timeout] = 120 + end + it "returns a default configuration hash with updated timeout value." do + expect(knife.ssh_opts).to eq(default_opts.merge(connection_timeout: 120)) + end + end + + end + + context "for winrm" do + let(:connection_protocol) { "winrm" } + it "returns an empty has because ssh is not winrm" do + expect(knife.ssh_opts).to eq({}) + end + end + + end + + context "#winrm_opts" do + let(:connection_protocol) { nil } + before do + allow(knife).to receive(:connection_protocol).and_return connection_protocol + end + + context "for winrm" do + let(:connection_protocol) { "winrm" } + let(:expected) do + { + winrm_transport: "negotiate", + winrm_basic_auth_only: false, + ssl: false, + ssl_peer_fingerprint: nil, + operation_timeout: 60, + } + end + + it "generates a correct configuration hash with expected defaults" do + expect(knife.winrm_opts).to eq expected + end + + context "with ssl_peer_fingerprint" do + let(:ssl_peer_fingerprint_expected) do + expected.merge({ ssl_peer_fingerprint: "ABCD" }) + end + + before do + knife.config[:winrm_ssl_peer_fingerprint] = "ABCD" + end + + it "generates a correct options hash with ssl_peer_fingerprint from the config provided" do + expect(knife.winrm_opts).to eq ssl_peer_fingerprint_expected + end + end + + context "with winrm_ssl" do + let(:ssl_expected) do + expected.merge({ ssl: true }) + end + before do + knife.config[:winrm_ssl] = true + end + + it "generates a correct options hash with ssl from the config provided" do + expect(knife.winrm_opts).to eq ssl_expected + end + end + + context "with winrm_auth_method" do + let(:winrm_auth_method_expected) do + expected.merge({ winrm_transport: "freeaccess" }) + end + + before do + knife.config[:winrm_auth_method] = "freeaccess" + end + + it "generates a correct options hash with winrm_transport from the config provided" do + expect(knife.winrm_opts).to eq winrm_auth_method_expected + end + end + + context "with ca_trust_file" do + let(:ca_trust_expected) do + expected.merge({ ca_trust_path: "/trust.me" }) + end + before do + knife.config[:ca_trust_file] = "/trust.me" + end + + it "generates a correct options hash with ca_trust_file from the config provided" do + expect(knife.winrm_opts).to eq ca_trust_expected + end + end + + context "with kerberos auth" do + let(:kerberos_expected) do + expected.merge({ + kerberos_service: "testsvc", + kerberos_realm: "TESTREALM", + winrm_transport: "kerberos", + }) + end + + before do + knife.config[:winrm_auth_method] = "kerberos" + knife.config[:kerberos_service] = "testsvc" + knife.config[:kerberos_realm] = "TESTREALM" + end + + it "generates a correct options hash containing kerberos auth configuration from the config provided" do + expect(knife.winrm_opts).to eq kerberos_expected + end + end + + context "with winrm_basic_auth_only" do + before do + knife.config[:winrm_basic_auth_only] = true + end + let(:basic_auth_expected) do + expected.merge( { winrm_basic_auth_only: true } ) + end + it "generates a correct options hash containing winrm_basic_auth_only from the config provided" do + expect(knife.winrm_opts).to eq basic_auth_expected + end + end + end + + context "for ssh" do + let(:connection_protocol) { "ssh" } + it "returns an empty hash because ssh is not winrm" do + expect(knife.winrm_opts).to eq({}) + end + end + end + describe "#run" do + it "performs the steps we expect to run a bootstrap" do + expect(knife).to receive(:check_license) + expect(knife).to receive(:validate_name_args!).ordered + expect(knife).to receive(:validate_protocol!).ordered + expect(knife).to receive(:validate_first_boot_attributes!).ordered + expect(knife).to receive(:validate_winrm_transport_opts!).ordered + expect(knife).to receive(:validate_policy_options!).ordered + expect(knife).to receive(:winrm_warn_no_ssl_verification).ordered + expect(knife).to receive(:warn_on_short_session_timeout).ordered + expect(knife).to receive(:connect!).ordered + expect(knife).to receive(:register_client).ordered + expect(knife).to receive(:render_template).and_return "content" + expect(knife).to receive(:upload_bootstrap).with("content").and_return "/remote/path.sh" + expect(knife).to receive(:perform_bootstrap).with("/remote/path.sh") + expect(connection).to receive(:del_file!) # Make sure cleanup happens + + knife.run + + # Post-run verify expected state changes (not many directly in #run) + expect($stdout.sync).to eq true + end + end + + describe "#register_client" do + let(:vault_handler_mock) { double("ChefVaultHandler") } + let(:client_builder_mock) { double("ClientBuilder") } + let(:node_name) { nil } + before do + allow(knife).to receive(:chef_vault_handler).and_return vault_handler_mock + allow(knife).to receive(:client_builder).and_return client_builder_mock + knife.config[:chef_node_name] = node_name + end + + shared_examples_for "creating the client locally" do + context "when a valid node name is present" do + let(:node_name) { "test" } + before do + allow(client_builder_mock).to receive(:client).and_return "client" + allow(client_builder_mock).to receive(:client_path).and_return "/key.pem" + end + + it "runs client_builder and vault_handler" do + expect(client_builder_mock).to receive(:run) + expect(vault_handler_mock).to receive(:run).with("client") + knife.register_client + end + + it "sets the path to the client key in the bootstrap context" do + allow(client_builder_mock).to receive(:run) + allow(vault_handler_mock).to receive(:run).with("client") + knife.register_client + expect(knife.bootstrap_context.client_pem).to eq "/key.pem" + end + end + + context "when no valid node name is present" do + let(:node_name) { nil } + it "shows an error and exits" do + expect(knife.ui).to receive(:error) + expect { knife.register_client }.to raise_error(SystemExit) + end + end + end + context "when chef_vault_handler says we're using vault" do + let(:vault_handler_mock) { double("ChefVaultHandler") } + before do + allow(vault_handler_mock).to receive(:doing_chef_vault?).and_return true + end + it_behaves_like "creating the client locally" + end + + context "when an non-existant validation key is specified in chef config" do + before do + Chef::Config[:validation_key] = "/blah" + allow(vault_handler_mock).to receive(:doing_chef_vault?).and_return false + allow(File).to receive(:exist?).with(%r{/blah}).and_return false + end + it_behaves_like "creating the client locally" + end + + context "when a valid validation key is given and we're doing old-style client creation" do + before do + Chef::Config[:validation_key] = "/blah" + allow(File).to receive(:exist?).with(%r{/blah}).and_return true + allow(vault_handler_mock).to receive(:doing_chef_vault?).and_return false + end + + it "shows a warning message" do + expect(knife.ui).to receive(:warn).twice + knife.register_client + end + end + end + + describe "#perform_bootstrap" do + let(:exit_status) { 0 } + let(:stdout) { "" } + let(:result_mock) { double("result", exit_status: exit_status, stderr: "A message", stdout: stdout) } + + before do + allow(connection).to receive(:hostname).and_return "testhost" + end + it "runs the remote script and logs the output" do + expect(knife.ui).to receive(:info).with(/Bootstrapping.*/) + expect(knife).to receive(:bootstrap_command) + .with("/path.sh") + .and_return("sh /path.sh") + expect(connection) + .to receive(:run_command) + .with("sh /path.sh") + .and_yield("output here", nil) + .and_return result_mock + + expect(knife.ui).to receive(:msg).with(/testhost/) + knife.perform_bootstrap("/path.sh") + end + + context "when the remote command fails" do + let(:exit_status) { 1 } + it "shows an error and exits" do + expect(knife.ui).to receive(:info).with(/Bootstrapping.*/) + expect(knife).to receive(:bootstrap_command) + .with("/path.sh") + .and_return("sh /path.sh") + expect(connection).to receive(:run_command).with("sh /path.sh").and_return result_mock + expect { knife.perform_bootstrap("/path.sh") }.to raise_error(SystemExit) + end + end + + context "when the remote command failed due to su auth error" do + let(:exit_status) { 1 } + let(:stdout) { "su: Authentication failure" } + let(:connection_obj) { double("connection", transport_options: {}) } + it "shows an error and exits" do + allow(connection).to receive(:connection).and_return(connection_obj) + expect(knife.ui).to receive(:info).with(/Bootstrapping.*/) + expect(knife).to receive(:bootstrap_command) + .with("/path.sh") + .and_return("su - USER -c 'sh /path.sh'") + expect(connection) + .to receive(:run_command) + .with("su - USER -c 'sh /path.sh'") + .and_yield("output here", nil) + .and_raise(Train::UserError) + expect { knife.perform_bootstrap("/path.sh") }.to raise_error(Train::UserError) + end + end + end + + describe "#connect!" do + before do + # These are not required at run-time because train will handle its own + # protocol loading. In this case, we're simulating train failures and have to load + # them ourselves. + require "net/ssh" + require "train/transports/ssh" + end + + context "in the normal case" do + it "connects using the connection_opts and notifies the operator of progress" do + expect(knife.ui).to receive(:info).with(/Connecting to.*/) + expect(knife).to receive(:connection_opts).and_return( { opts: "here" }) + expect(knife).to receive(:do_connect).with( { opts: "here" } ) + knife.connect! + end + end + + context "when a general non-auth-failure occurs" do + let(:expected_error) { RuntimeError.new } + before do + allow(knife).to receive(:do_connect).and_raise(expected_error) + end + it "re-raises the exception" do + expect { knife.connect! }.to raise_error(expected_error) + end + end + + context "when ssh fingerprint is invalid" do + let(:expected_error) { Train::Error.new("fingerprint AA:BB is unknown for \"blah,127.0.0.1\"") } + before do + allow(knife).to receive(:do_connect).and_raise(expected_error) + end + it "warns, prompts to accept, then connects with verify_host_key of accept_new" do + expect(knife).to receive(:do_connect).and_raise(expected_error) + expect(knife.ui).to receive(:confirm) + .with(/.*host 'blah \(127.0.0.1\)'.*AA:BB.*Are you sure you want to continue.*/m) + .and_return(true) + expect(knife).to receive(:do_connect) do |opts| + expect(opts[:verify_host_key]).to eq :accept_new + end + knife.connect! + end + end + + context "when an auth failure occurs" do + let(:expected_error) do + e = Train::Error.new + actual = Net::SSH::AuthenticationFailed.new + # Simulate train's nested error - they wrap + # ssh/network errors in a TrainError. + allow(e).to receive(:cause).and_return(actual) + e + end + + let(:expected_error_password_prompt) do + e = Train::ClientError.new + reason = :no_ssh_password_or_key_available + allow(e).to receive(:reason).and_return(reason) + e + end + + let(:expected_error_password_prompt_winrm) do + e = RuntimeError.new + message = "password is a required option" + allow(e).to receive(:message).and_return(message) + e + end + + context "and password auth was used" do + before do + allow(connection).to receive(:password_auth?).and_return true + end + + it "re-raises the error so as not to resubmit the same failing password" do + expect(knife).to receive(:do_connect).and_raise(expected_error) + expect { knife.connect! }.to raise_error(expected_error) + end + end + + context "and password auth was not used" do + before do + allow(connection).to receive(:password_auth?).and_return false + allow(connection).to receive(:user).and_return "testuser" + allow(knife).to receive(:connection_protocol).and_return connection_protocol + end + + context "when using ssh" do + let(:connection_protocol) { "ssh" } + + it "warns, prompts for password, then reconnects with a password-enabled configuration using the new password" do + expect(knife).to receive(:do_connect).and_raise(expected_error_password_prompt) + expect(knife.ui).to receive(:warn).with(/Failed to auth.*/) + expect(knife.ui).to receive(:ask).and_return("newpassword") + # Ensure that we set echo off to prevent showing password on the screen + expect(knife).to receive(:do_connect) do |opts| + expect(opts[:password]).to eq "newpassword" + end + knife.connect! + end + end + + context "when using winrm" do + let(:connection_protocol) { "winrm" } + + it "warns, prompts for password, then reconnects with a password-enabled configuration using the new password for" do + expect(knife).to receive(:do_connect).and_raise(expected_error_password_prompt_winrm) + expect(knife.ui).to receive(:warn).with(/Failed to auth.*/) + expect(knife.ui).to receive(:ask).and_return("newpassword") + # Ensure that we set echo off to prevent showing password on the screen + expect(knife).to receive(:do_connect) do |opts| + expect(opts[:password]).to eq "newpassword" + end + knife.connect! + end + end + end + end + end + + it "verifies that a server to bootstrap was given as a command line arg" do + knife.name_args = nil + expect(knife).to receive(:check_license) + expect { knife.run }.to raise_error(SystemExit) + expect(stderr.string).to match(/ERROR:.+FQDN or ip/) + end + + describe "#bootstrap_context" do + context "under Windows" do + let(:windows_test) { true } + it "creates a WindowsBootstrapContext" do + require "chef/knife/core/windows_bootstrap_context" + expect(knife.bootstrap_context.class).to eq Chef::Knife::Core::WindowsBootstrapContext + end + end + + context "under linux" do + let(:linux_test) { true } + it "creates a BootstrapContext" do + require "chef/knife/core/bootstrap_context" + expect(knife.bootstrap_context.class).to eq Chef::Knife::Core::BootstrapContext + end + end + end + + describe "#config_value" do + before do + knife.config[:test_key_a] = "a from cli" + knife.config[:test_key_b] = "b from cli" + Chef::Config[:knife][:test_key_a] = "a from Chef::Config" + Chef::Config[:knife][:test_key_c] = "c from Chef::Config" + Chef::Config[:knife][:alt_test_key_c] = "alt c from Chef::Config" + knife.merge_configs + Chef::Config[:treat_deprecation_warnings_as_errors] = false + end + + it "returns the Chef::Config value from the cli when the CLI key is set" do + expect(knife.config_value(:test_key_a, :alt_test_key_c)).to eq "a from cli" + end + + it "returns the Chef::Config value from the alternative key when the CLI key is not set" do + expect(knife.config_value(:test_key_d, :alt_test_key_c)).to eq "alt c from Chef::Config" + end + + it "returns the default value when the key is not provided by CLI or Chef::Config" do + expect(knife.config_value(:missing_key, :missing_key, "found")).to eq "found" + end + end + + describe "#upload_bootstrap" do + before do + allow(connection).to receive(:temp_dir).and_return(temp_dir) + allow(connection).to receive(:normalize_path) { |a| a } + end + + let(:content) { "bootstrap script content" } + context "under Windows" do + let(:windows_test) { true } + let(:temp_dir) { "C:/Temp/bootstrap" } + it "creates a bat file in the temp dir provided by connection, using given content" do + expect(connection).to receive(:upload_file_content!).with(content, "C:/Temp/bootstrap/bootstrap.bat") + expect(knife.upload_bootstrap(content)).to eq "C:/Temp/bootstrap/bootstrap.bat" + end + end + + context "under Linux" do + let(:linux_test) { true } + let(:temp_dir) { "/tmp/bootstrap" } + it "creates a 'sh file in the temp dir provided by connection, using given content" do + expect(connection).to receive(:upload_file_content!).with(content, "/tmp/bootstrap/bootstrap.sh") + expect(knife.upload_bootstrap(content)).to eq "/tmp/bootstrap/bootstrap.sh" + end + end + end + + describe "#bootstrap_command" do + context "under Windows" do + let(:windows_test) { true } + it "prefixes the command to run under cmd.exe" do + expect(knife.bootstrap_command("autoexec.bat")).to eq "cmd.exe /C autoexec.bat" + end + + end + context "under Linux" do + let(:linux_test) { true } + it "prefixes the command to run under sh" do + expect(knife.bootstrap_command("bootstrap.sh")).to eq "sh bootstrap.sh" + end + + context "with --su-user option" do + let(:connection_obj) { double("connection", transport_options: {}) } + before do + knife.config[:su_user] = "root" + allow(connection).to receive(:connection).and_return(connection_obj) + end + it "prefixes the command to run using su -USER -c" do + expect(knife.bootstrap_command("bootstrap.sh")).to eq "su - #{knife.config[:su_user]} -c 'sh bootstrap.sh'" + expect(connection_obj.transport_options.key?(:pty)).to eq true + end + + it "sudo appended if --sudo option enabled" do + knife.config[:use_sudo] = true + expect(knife.bootstrap_command("bootstrap.sh")).to eq "sudo su - #{knife.config[:su_user]} -c 'sh bootstrap.sh'" + expect(connection_obj.transport_options.key?(:pty)).to eq true + end + end + end + end + + describe "#default_bootstrap_template" do + context "under Windows" do + let(:windows_test) { true } + it "is windows-chef-client-msi" do + expect(knife.default_bootstrap_template).to eq "windows-chef-client-msi" + end + + end + context "under Linux" do + let(:linux_test) { true } + it "is chef-full" do + expect(knife.default_bootstrap_template).to eq "chef-full" + end + end + end + + describe "#do_connect" do + let(:host_descriptor) { "example.com" } + let(:connection) { double("TrainConnector") } + let(:connector_mock) { double("TargetResolver", targets: [ connection ]) } + before do + allow(knife).to receive(:host_descriptor).and_return host_descriptor + end + + it "creates a TrainConnector and connects it" do + expect(Chef::Knife::Bootstrap::TrainConnector).to receive(:new).and_return connection + expect(connection).to receive(:connect!) + knife.do_connect({}) + end + + context "when sshd configured with requiretty" do + let(:pty_err_msg) { "Sudo requires a TTY. Please see the README on how to configure sudo to allow for non-interactive usage." } + let(:expected_error) { Train::UserError.new(pty_err_msg, :sudo_no_tty) } + before do + allow(connection).to receive(:connect!).and_raise(expected_error) + end + it "retry with pty true request option" do + expect(Chef::Knife::Bootstrap::TrainConnector).to receive(:new).and_return(connection).exactly(2).times + expect(knife.ui).to receive(:warn).with("#{pty_err_msg} - trying with pty request") + expect { knife.do_connect({}) }.to raise_error(expected_error) + end + end + end + + describe "validate_winrm_transport_opts!" do + before do + allow(knife).to receive(:connection_protocol).and_return connection_protocol + end + + context "when using ssh" do + let(:connection_protocol) { "ssh" } + it "returns true" do + expect(knife.validate_winrm_transport_opts!).to eq true + end + end + context "when using winrm" do + let(:connection_protocol) { "winrm" } + context "with plaintext auth" do + before do + knife.config[:winrm_auth_method] = "plaintext" + end + context "with ssl" do + before do + knife.config[:winrm_ssl] = true + end + it "will not error because we won't send anything in plaintext regardless" do + expect(knife.validate_winrm_transport_opts!).to eq true + end + end + context "without ssl" do + before do + knife.config[:winrm_ssl] = false + end + context "and no validation key exists" do + before do + Chef::Config[:validation_key] = "validation_key.pem" + allow(File).to receive(:exist?).with(/.*validation_key.pem/).and_return false + end + + it "will error because we will generate and send a client key over the wire in plaintext" do + expect { knife.validate_winrm_transport_opts! }.to raise_error(SystemExit) + end + + end + context "and a validation key exists" do + before do + Chef::Config[:validation_key] = "validation_key.pem" + allow(File).to receive(:exist?).with(/.*validation_key.pem/).and_return true + end + # TODO - don't we still send validation key? + it "will not error because we don not send client key over the wire" do + expect(knife.validate_winrm_transport_opts!).to eq true + end + end + end + end + + context "with other auth" do + before do + knife.config[:winrm_auth_method] = "kerberos" + end + + context "and no validation key exists" do + before do + + Chef::Config[:validation_key] = "validation_key.pem" + allow(File).to receive(:exist?).with(/.*validation_key.pem/).and_return false + end + + it "will not error because we're not using plaintext auth" do + expect(knife.validate_winrm_transport_opts!).to eq true + end + end + context "and a validation key exists" do + before do + Chef::Config[:validation_key] = "validation_key.pem" + allow(File).to receive(:exist?).with(/.*validation_key.pem/).and_return true + end + + it "will not error because a client key won't be sent over the wire in plaintext when a validation key is present" do + expect(knife.validate_winrm_transport_opts!).to eq true + end + end + + end + + end + + end + + describe "#winrm_warn_no_ssl_verification" do + before do + allow(knife).to receive(:connection_protocol).and_return connection_protocol + end + + context "when using ssh" do + let(:connection_protocol) { "ssh" } + it "does not issue a warning" do + expect(knife.ui).to_not receive(:warn) + knife.winrm_warn_no_ssl_verification + end + end + context "when using winrm" do + let(:connection_protocol) { "winrm" } + context "winrm_no_verify_cert is set" do + before do + knife.config[:winrm_no_verify_cert] = true + end + + context "and ca_trust_file is present" do + before do + knife.config[:ca_trust_file] = "file" + end + + it "does not issue a warning" do + expect(knife.ui).to_not receive(:warn) + knife.winrm_warn_no_ssl_verification + end + end + + context "and winrm_ssl_peer_fingerprint is present" do + before do + knife.config[:winrm_ssl_peer_fingerprint] = "ABCD" + end + it "does not issue a warning" do + expect(knife.ui).to_not receive(:warn) + knife.winrm_warn_no_ssl_verification + end + end + context "and neither ca_trust_file nor winrm_ssl_peer_fingerprint is present" do + it "issues a warning" do + expect(knife.ui).to receive(:warn) + knife.winrm_warn_no_ssl_verification + end + end + end + end + end + + describe "#warn_on_short_session_timeout" do + let(:session_timeout) { 60 } + + before do + allow(knife).to receive(:session_timeout).and_return(session_timeout) + end + + context "timeout is not set at all" do + let(:session_timeout) { nil } + it "does not issue a warning" do + expect(knife.ui).to_not receive(:warn) + knife.warn_on_short_session_timeout + end + end + + context "timeout is more than 15" do + let(:session_timeout) { 16 } + it "does not issue a warning" do + expect(knife.ui).to_not receive(:warn) + knife.warn_on_short_session_timeout + end + end + context "timeout is 15 or less" do + let(:session_timeout) { 15 } + it "issues a warning" do + expect(knife.ui).to receive(:warn) + knife.warn_on_short_session_timeout + end + end + end +end diff --git a/knife/spec/unit/knife/client_bulk_delete_spec.rb b/knife/spec/unit/knife/client_bulk_delete_spec.rb new file mode 100644 index 0000000000..86d69ff4d6 --- /dev/null +++ b/knife/spec/unit/knife/client_bulk_delete_spec.rb @@ -0,0 +1,166 @@ +# +# Author:: Stephen Delano (<stephen@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" + +describe Chef::Knife::ClientBulkDelete do + let(:stdout_io) { StringIO.new } + let(:stdout) { stdout_io.string } + let(:stderr_io) { StringIO.new } + let(:stderr) { stderr_io.string } + + let(:knife) do + k = Chef::Knife::ClientBulkDelete.new + k.name_args = name_args + k.config = option_args + allow(k.ui).to receive(:stdout).and_return(stdout_io) + allow(k.ui).to receive(:stderr).and_return(stderr_io) + allow(k.ui).to receive(:confirm).and_return(knife_confirm) + allow(k.ui).to receive(:confirm_without_exit).and_return(knife_confirm) + k + end + + let(:name_args) { [ "." ] } + let(:option_args) { {} } + + let(:knife_confirm) { true } + + let(:nonvalidator_client_names) { %w{tim dan stephen} } + let(:nonvalidator_clients) do + clients = {} + + nonvalidator_client_names.each do |client_name| + client = Chef::ApiClientV1.new + client.name(client_name) + allow(client).to receive(:destroy).and_return(true) + clients[client_name] = client + end + + clients + end + + let(:validator_client_names) { %w{myorg-validator} } + let(:validator_clients) do + clients = {} + + validator_client_names.each do |validator_client_name| + validator_client = Chef::ApiClientV1.new + validator_client.name(validator_client_name) + allow(validator_client).to receive(:validator).and_return(true) + allow(validator_client).to receive(:destroy).and_return(true) + clients[validator_client_name] = validator_client + end + + clients + end + + let(:client_names) { nonvalidator_client_names + validator_client_names } + let(:clients) do + nonvalidator_clients.merge(validator_clients) + end + + before(:each) do + allow(Chef::ApiClientV1).to receive(:list).and_return(clients) + end + + describe "run" do + describe "without a regex" do + let(:name_args) { [ ] } + + it "should exit if the regex is not provided" do + expect { knife.run }.to raise_error(SystemExit) + end + end + + describe "with any clients" do + it "should get the list of the clients" do + expect(Chef::ApiClientV1).to receive(:list) + knife.run + end + + it "should print the name of the clients" do + knife.run + client_names.each do |client_name| + expect(stdout).to include(client_name) + end + end + + it "should confirm you really want to delete them" do + expect(knife.ui).to receive(:confirm) + knife.run + end + + describe "without --delete-validators" do + it "should mention that validator clients wont be deleted" do + knife.run + expect(stdout).to include("The following clients are validators and will not be deleted:") + info = stdout.index "The following clients are validators and will not be deleted:" + val = stdout.index "myorg-validator" + expect(val > info).to be_truthy + end + + it "should only delete nonvalidator clients" do + nonvalidator_clients.each_value do |c| + expect(c).to receive(:destroy) + end + + validator_clients.each_value do |c| + expect(c).not_to receive(:destroy) + end + + knife.run + end + end + + describe "with --delete-validators" do + let(:option_args) { { delete_validators: true } } + + it "should mention that validator clients will be deleted" do + knife.run + expect(stdout).to include("The following validators will be deleted") + end + + it "should confirm twice" do + expect(knife.ui).to receive(:confirm).once + expect(knife.ui).to receive(:confirm_without_exit).once + knife.run + end + + it "should delete all clients" do + clients.each_value do |c| + expect(c).to receive(:destroy) + end + + knife.run + end + end + end + + describe "with some clients" do + let(:name_args) { [ "^ti" ] } + + it "should only delete clients that match the regex" do + expect(clients["tim"]).to receive(:destroy) + expect(clients["stephen"]).not_to receive(:destroy) + expect(clients["dan"]).not_to receive(:destroy) + expect(clients["myorg-validator"]).not_to receive(:destroy) + knife.run + end + end + end +end diff --git a/knife/spec/unit/knife/client_create_spec.rb b/knife/spec/unit/knife/client_create_spec.rb new file mode 100644 index 0000000000..48a7e71df5 --- /dev/null +++ b/knife/spec/unit/knife/client_create_spec.rb @@ -0,0 +1,169 @@ +# +# Author:: Adam Jacob (<adam@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" + +Chef::Knife::ClientCreate.load_deps + +describe Chef::Knife::ClientCreate do + let(:stderr) { StringIO.new } + let(:stdout) { StringIO.new } + + let(:default_client_hash) do + { + "name" => "adam", + "validator" => false, + } + end + + let(:client) do + Chef::ApiClientV1.new + end + + let(:knife) do + k = Chef::Knife::ClientCreate.new + k.name_args = [] + allow(k).to receive(:client).and_return(client) + allow(k).to receive(:edit_hash).with(client).and_return(client) + allow(k.ui).to receive(:stderr).and_return(stderr) + allow(k.ui).to receive(:stdout).and_return(stdout) + k + end + + before do + allow(client).to receive(:to_s).and_return("client[adam]") + allow(knife).to receive(:create_client).and_return(client) + end + + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + end + + describe "run" do + context "when nothing is passed" do + # from spec/support/shared/unit/knife_shared.rb + it_should_behave_like "mandatory field missing" do + let(:name_args) { [] } + let(:fieldname) { "client name" } + end + end + + context "when clientname is passed" do + before do + knife.name_args = ["adam"] + end + + context "when public_key and prevent_keygen are passed" do + before do + knife.config[:public_key] = "some_key" + knife.config[:prevent_keygen] = true + end + + it "prints the usage" do + expect(knife).to receive(:show_usage) + expect { knife.run }.to raise_error(SystemExit) + end + + it "prints a relevant error message" do + expect { knife.run }.to raise_error(SystemExit) + expect(stderr.string).to match(/You cannot pass --public-key and --prevent-keygen/) + end + end + + it "should create the ApiClient" do + expect(knife).to receive(:create_client) + knife.run + end + + it "should print a message upon creation" do + expect(knife).to receive(:create_client) + knife.run + expect(stderr.string).to match(/Created client.*adam/i) + end + + it "should set the Client name" do + knife.run + expect(client.name).to eq("adam") + end + + it "by default it is not a validator" do + knife.run + expect(client.validator).to be_falsey + end + + it "by default it should set create_key to true" do + knife.run + expect(client.create_key).to be_truthy + end + + it "should allow you to edit the data" do + expect(knife).to receive(:edit_hash).with(client).and_return(client) + knife.run + end + + describe "with -f or --file" do + before do + client.private_key "woot" + end + + it "should write the private key to a file" do + knife.config[:file] = "/tmp/monkeypants" + filehandle = double("Filehandle") + expect(filehandle).to receive(:print).with("woot") + expect(File).to receive(:open).with("/tmp/monkeypants", "w").and_yield(filehandle) + knife.run + end + end + + describe "with -p or --public-key" do + before do + knife.config[:public_key] = "some_key" + allow(File).to receive(:read).and_return("some_key") + allow(File).to receive(:expand_path) + end + + it "sets the public key" do + knife.run + expect(client.public_key).to eq("some_key") + end + end + + describe "with -k or --prevent-keygen" do + before do + knife.config[:prevent_keygen] = true + end + + it "does not set create_key" do + knife.run + expect(client.create_key).to be_falsey + end + end + + describe "with --validator" do + before do + knife.config[:validator] = true + end + + it "should create an validator client" do + knife.run + expect(client.validator).to be_truthy + end + end + end + end +end diff --git a/knife/spec/unit/knife/client_delete_spec.rb b/knife/spec/unit/knife/client_delete_spec.rb new file mode 100644 index 0000000000..ec20878ade --- /dev/null +++ b/knife/spec/unit/knife/client_delete_spec.rb @@ -0,0 +1,99 @@ +# +# Author:: Thomas Bishop (<bishop.thomas@gmail.com>) +# Copyright:: Copyright 2011-2016, Thomas Bishop +# 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" + +describe Chef::Knife::ClientDelete do + before(:each) do + @knife = Chef::Knife::ClientDelete.new + # defaults + @knife.config = { + delete_validators: false, + } + @knife.name_args = [ "adam" ] + end + + describe "run" do + it "should delete the client" do + expect(@knife).to receive(:delete_object).with(Chef::ApiClientV1, "adam", "client") + @knife.run + end + + context "receives multiple clients" do + let(:clients) { %w{ adam ben charlie } } + + before(:each) do + @knife.name_args = clients + end + + it "deletes all clients" do + clients.each do |client| + expect(@knife).to receive(:delete_object).with(Chef::ApiClientV1, client, "client") + end + + @knife.run + end + end + + it "should print usage and exit when a client name is not provided" do + @knife.name_args = [] + expect(@knife).to receive(:show_usage) + expect(@knife.ui).to receive(:fatal) + expect { @knife.run }.to raise_error(SystemExit) + end + end + + describe "with a validator" do + before(:each) do + allow(Chef::Knife::UI).to receive(:confirm).and_return(true) + allow(@knife).to receive(:confirm).and_return(true) + @client = Chef::ApiClientV1.new + expect(Chef::ApiClientV1).to receive(:load).and_return(@client) + end + + it "should delete non-validator client if --delete-validators is not set" do + @knife.config[:delete_validators] = false + expect(@client).to receive(:destroy).and_return(@client) + expect(@knife).to receive(:msg) + + @knife.run + end + + it "should delete non-validator client if --delete-validators is set" do + @knife.config[:delete_validators] = true + expect(@client).to receive(:destroy).and_return(@client) + expect(@knife).to receive(:msg) + + @knife.run + end + + it "should not delete validator client if --delete-validators is not set" do + @client.validator(true) + expect(@knife.ui).to receive(:fatal) + expect { @knife.run }.to raise_error(SystemExit) + end + + it "should delete validator client if --delete-validators is set" do + @knife.config[:delete_validators] = true + expect(@client).to receive(:destroy).and_return(@client) + expect(@knife).to receive(:msg) + + @knife.run + end + end +end diff --git a/knife/spec/unit/knife/client_edit_spec.rb b/knife/spec/unit/knife/client_edit_spec.rb new file mode 100644 index 0000000000..03ac450b3e --- /dev/null +++ b/knife/spec/unit/knife/client_edit_spec.rb @@ -0,0 +1,53 @@ +# +# Author:: Thomas Bishop (<bishop.thomas@gmail.com>) +# Copyright:: Copyright 2011-2016, Thomas Bishop +# 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 "chef/api_client_v1" + +describe Chef::Knife::ClientEdit do + before(:each) do + @knife = Chef::Knife::ClientEdit.new + @knife.name_args = [ "adam" ] + @knife.config[:disable_editing] = true + end + + describe "run" do + let(:data) do + { + "name" => "adam", + "validator" => false, + "admin" => false, + "chef_type" => "client", + "create_key" => true, + } + end + + it "should edit the client" do + allow(Chef::ApiClientV1).to receive(:load).with("adam").and_return(data) + expect(@knife).to receive(:edit_hash).with(data).and_return(data) + @knife.run + end + + it "should print usage and exit when a client name is not provided" do + @knife.name_args = [] + expect(@knife).to receive(:show_usage) + expect(@knife.ui).to receive(:fatal) + expect { @knife.run }.to raise_error(SystemExit) + end + end +end diff --git a/knife/spec/unit/knife/client_list_spec.rb b/knife/spec/unit/knife/client_list_spec.rb new file mode 100644 index 0000000000..b6a205e6b1 --- /dev/null +++ b/knife/spec/unit/knife/client_list_spec.rb @@ -0,0 +1,34 @@ +# +# Author:: Thomas Bishop (<bishop.thomas@gmail.com>) +# Copyright:: Copyright 2011-2016, Thomas Bishop +# 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" + +describe Chef::Knife::ClientList do + before(:each) do + @knife = Chef::Knife::ClientList.new + @knife.name_args = [ "adam" ] + end + + describe "run" do + it "should list the clients" do + expect(Chef::ApiClientV1).to receive(:list) + expect(@knife).to receive(:format_list_for_display) + @knife.run + end + end +end diff --git a/knife/spec/unit/knife/client_reregister_spec.rb b/knife/spec/unit/knife/client_reregister_spec.rb new file mode 100644 index 0000000000..f1217edee4 --- /dev/null +++ b/knife/spec/unit/knife/client_reregister_spec.rb @@ -0,0 +1,62 @@ +# +# Author:: Thomas Bishop (<bishop.thomas@gmail.com>) +# Copyright:: Copyright 2011-2016, Thomas Bishop +# 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" + +describe Chef::Knife::ClientReregister do + before(:each) do + @knife = Chef::Knife::ClientReregister.new + @knife.name_args = [ "adam" ] + @client_mock = double("client_mock", private_key: "foo_key") + @stdout = StringIO.new + allow(@knife.ui).to receive(:stdout).and_return(@stdout) + end + + context "when no client name is given on the command line" do + before do + @knife.name_args = [] + end + + it "should print usage and exit when a client name is not provided" do + expect(@knife).to receive(:show_usage) + expect(@knife.ui).to receive(:fatal) + expect { @knife.run }.to raise_error(SystemExit) + end + end + + context "when not configured for file output" do + it "reregisters the client and prints the key" do + expect(Chef::ApiClientV1).to receive(:reregister).with("adam").and_return(@client_mock) + @knife.run + expect(@stdout.string).to match( /foo_key/ ) + end + end + + context "when configured for file output" do + it "should write the private key to a file" do + expect(Chef::ApiClientV1).to receive(:reregister).with("adam").and_return(@client_mock) + + @knife.config[:file] = "/tmp/monkeypants" + filehandle = StringIO.new + expect(File).to receive(:open).with("/tmp/monkeypants", "w").and_yield(filehandle) + @knife.run + expect(filehandle.string).to eq("foo_key") + end + end + +end diff --git a/knife/spec/unit/knife/client_show_spec.rb b/knife/spec/unit/knife/client_show_spec.rb new file mode 100644 index 0000000000..39928a6289 --- /dev/null +++ b/knife/spec/unit/knife/client_show_spec.rb @@ -0,0 +1,52 @@ +# +# Author:: Thomas Bishop (<bishop.thomas@gmail.com>) +# Copyright:: Copyright 2011-2016, Thomas Bishop +# 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" + +describe Chef::Knife::ClientShow do + before(:each) do + @knife = Chef::Knife::ClientShow.new + @knife.name_args = [ "adam" ] + @client_mock = double("client_mock") + end + + describe "run" do + it "should list the client" do + expect(Chef::ApiClientV1).to receive(:load).with("adam").and_return(@client_mock) + expect(@knife).to receive(:format_for_display).with(@client_mock) + @knife.run + end + + it "should pretty print json" do + @knife.config[:format] = "json" + @stdout = StringIO.new + allow(@knife.ui).to receive(:stdout).and_return(@stdout) + fake_client_contents = { "foo" => "bar", "baz" => "qux" } + expect(Chef::ApiClientV1).to receive(:load).with("adam").and_return(fake_client_contents) + @knife.run + expect(@stdout.string).to eql("{\n \"foo\": \"bar\",\n \"baz\": \"qux\"\n}\n") + end + + it "should print usage and exit when a client name is not provided" do + @knife.name_args = [] + expect(@knife).to receive(:show_usage) + expect(@knife.ui).to receive(:fatal) + expect { @knife.run }.to raise_error(SystemExit) + end + end +end diff --git a/knife/spec/unit/knife/configure_client_spec.rb b/knife/spec/unit/knife/configure_client_spec.rb new file mode 100644 index 0000000000..f88ffb31ed --- /dev/null +++ b/knife/spec/unit/knife/configure_client_spec.rb @@ -0,0 +1,81 @@ +# +# Author:: Thomas Bishop (<bishop.thomas@gmail.com>) +# Copyright:: Copyright 2011-2016, Thomas Bishop +# 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" + +describe Chef::Knife::ConfigureClient do + before do + @knife = Chef::Knife::ConfigureClient.new + Chef::Config[:chef_server_url] = "https://chef.example.com" + Chef::Config[:validation_client_name] = "chef-validator" + Chef::Config[:validation_key] = "/etc/chef/validation.pem" + + @stderr = StringIO.new + allow(@knife.ui).to receive(:stderr).and_return(@stderr) + end + + describe "run" do + it "should print usage and exit when a directory is not provided" do + expect(@knife).to receive(:show_usage) + expect(@knife.ui).to receive(:fatal).with(/must provide the directory/) + expect do + @knife.run + end.to raise_error SystemExit + end + + describe "when specifing a directory" do + before do + @knife.name_args = ["/home/bob/.chef"] + @client_file = StringIO.new + @validation_file = StringIO.new + expect(File).to receive(:open).with("/home/bob/.chef/client.rb", "w") + .and_yield(@client_file) + expect(File).to receive(:open).with("/home/bob/.chef/validation.pem", "w") + .and_yield(@validation_file) + expect(IO).to receive(:read).and_return("foo_bar_baz") + end + + it "should recursively create the directory" do + expect(FileUtils).to receive(:mkdir_p).with("/home/bob/.chef") + @knife.run + end + + it "should write out the config file" do + allow(FileUtils).to receive(:mkdir_p) + @knife.run + expect(@client_file.string).to match %r{chef_server_url\s+'https\://chef\.example\.com'} + expect(@client_file.string).to match(/validation_client_name\s+'chef-validator'/) + end + + it "should write out the validation.pem file" do + allow(FileUtils).to receive(:mkdir_p) + @knife.run + expect(@validation_file.string).to match(/foo_bar_baz/) + end + + it "should print information on what is being configured" do + allow(FileUtils).to receive(:mkdir_p) + @knife.run + expect(@stderr.string).to match(/creating client configuration/i) + expect(@stderr.string).to match(/writing client\.rb/i) + expect(@stderr.string).to match(/writing validation\.pem/i) + end + end + end + +end diff --git a/knife/spec/unit/knife/configure_spec.rb b/knife/spec/unit/knife/configure_spec.rb new file mode 100644 index 0000000000..f9d1bea8fd --- /dev/null +++ b/knife/spec/unit/knife/configure_spec.rb @@ -0,0 +1,190 @@ +require "knife_spec_helper" + +describe Chef::Knife::Configure do + before do + Chef::Log.logger = Logger.new(StringIO.new) + + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::Configure.new + @rest_client = double("null rest client", post: { result: :true }) + allow(@knife).to receive(:rest).and_return(@rest_client) + + @out = StringIO.new + allow(@knife.ui).to receive(:stdout).and_return(@out) + @knife.config[:config_file] = "/home/you/.chef/knife.rb" + + @in = StringIO.new("\n" * 7) + allow(@knife.ui).to receive(:stdin).and_return(@in) + + @err = StringIO.new + allow(@knife.ui).to receive(:stderr).and_return(@err) + + allow(Ohai::System).to receive(:new).and_return(ohai) + end + + let(:fqdn) { "foo.example.org" } + + let(:ohai) do + o = {} + allow(o).to receive(:all_plugins).with(%w{ os hostname fqdn }) + o[:fqdn] = fqdn + o + end + + let(:default_admin_key) { "/etc/chef-server/admin.pem" } + let(:default_admin_key_win32) { File.expand_path(default_admin_key) } + + let(:default_validator_key) { "/etc/chef-server/chef-validator.pem" } + let(:default_validator_key_win32) { File.expand_path(default_validator_key) } + + let(:default_server_url) { "https://#{fqdn}/organizations/myorg" } + + it "asks the user for the URL of the chef server" do + @knife.ask_user_for_config + expect(@out.string).to match(Regexp.escape("Please enter the chef server URL: [#{default_server_url}]")) + expect(@knife.chef_server).to eq(default_server_url) + end + + it "asks the user for the clientname they want for the new client if -i is specified" do + @knife.config[:initial] = true + allow(Etc).to receive(:getlogin).and_return("a-new-user") + @knife.ask_user_for_config + expect(@out.string).to match(Regexp.escape("Please enter a name for the new user: [a-new-user]")) + expect(@knife.new_client_name).to eq(Etc.getlogin) + end + + it "should not ask the user for the clientname they want for the new client if -i and --node_name are specified" do + @knife.config[:initial] = true + @knife.config[:node_name] = "testnode" + allow(Etc).to receive(:getlogin).and_return("a-new-user") + @knife.ask_user_for_config + expect(@out.string).not_to match(Regexp.escape("Please enter a name for the new user")) + expect(@knife.new_client_name).to eq("testnode") + end + + it "asks the user for the existing API username or clientname if -i is not specified" do + allow(Etc).to receive(:getlogin).and_return("a-new-user") + @knife.ask_user_for_config + expect(@out.string).to match(Regexp.escape("Please enter an existing username or clientname for the API: [a-new-user]")) + expect(@knife.new_client_name).to eq(Etc.getlogin) + end + + it "asks the user for the existing admin client's name if -i is specified" do + @knife.config[:initial] = true + @knife.ask_user_for_config + expect(@out.string).to match(Regexp.escape("Please enter the existing admin name: [admin]")) + expect(@knife.admin_client_name).to eq("admin") + end + + it "should not ask the user for the existing admin client's name if -i and --admin-client_name are specified" do + @knife.config[:initial] = true + @knife.config[:admin_client_name] = "my-webui" + @knife.ask_user_for_config + expect(@out.string).not_to match(Regexp.escape("Please enter the existing admin:")) + expect(@knife.admin_client_name).to eq("my-webui") + end + + it "should not ask the user for the existing admin client's name if -i is not specified" do + @knife.ask_user_for_config + expect(@out.string).not_to match(Regexp.escape("Please enter the existing admin: [admin]")) + expect(@knife.admin_client_name).not_to eq("admin") + end + + it "asks the user for the location of the existing admin key if -i is specified" do + @knife.config[:initial] = true + @knife.ask_user_for_config + expect(@out.string).to match(Regexp.escape("Please enter the location of the existing admin's private key: [#{default_admin_key}]")) + if windows? + expect(@knife.admin_client_key.capitalize).to eq(default_admin_key_win32.capitalize) + else + expect(@knife.admin_client_key).to eq(default_admin_key) + end + end + + it "should not ask the user for the location of the existing admin key if -i and --admin_client_key are specified" do + @knife.config[:initial] = true + @knife.config[:admin_client_key] = "/home/you/.chef/my-webui.pem" + @knife.ask_user_for_config + expect(@out.string).not_to match(Regexp.escape("Please enter the location of the existing admin client's private key:")) + if windows? + expect(@knife.admin_client_key).to match %r{^[A-Za-z]:/home/you/\.chef/my-webui\.pem$} + else + expect(@knife.admin_client_key).to eq("/home/you/.chef/my-webui.pem") + end + end + + it "should not ask the user for the location of the existing admin key if -i is not specified" do + @knife.ask_user_for_config + expect(@out.string).not_to match(Regexp.escape("Please enter the location of the existing admin client's private key: [#{default_admin_key}]")) + if windows? + expect(@knife.admin_client_key).not_to eq(default_admin_key_win32) + else + expect(@knife.admin_client_key).not_to eq(default_admin_key) + end + end + + it "should not ask the user for anything if -i and all other properties are specified" do + @knife.config[:initial] = true + @knife.config[:chef_server_url] = "http://localhost:5000" + @knife.config[:node_name] = "testnode" + @knife.config[:admin_client_name] = "my-webui" + @knife.config[:admin_client_key] = "/home/you/.chef/my-webui.pem" + @knife.config[:client_key] = "/home/you/a-new-user.pem" + allow(Etc).to receive(:getlogin).and_return("a-new-user") + + @knife.ask_user_for_config + expect(@out.string).to match(/\s*/) + + expect(@knife.new_client_name).to eq("testnode") + expect(@knife.chef_server).to eq("http://localhost:5000") + expect(@knife.admin_client_name).to eq("my-webui") + if windows? + expect(@knife.admin_client_key).to match %r{^[A-Za-z]:/home/you/\.chef/my-webui\.pem$} + expect(@knife.new_client_key).to match %r{^[A-Za-z]:/home/you/a-new-user\.pem$} + else + expect(@knife.admin_client_key).to eq("/home/you/.chef/my-webui.pem") + expect(@knife.new_client_key).to eq("/home/you/a-new-user.pem") + end + end + + it "writes the new data to a config file" do + allow(Chef::Util::PathHelper).to receive(:home).with(".chef").and_return("/home/you/.chef") + allow(File).to receive(:expand_path).with("/home/you/.chef/credentials").and_return("/home/you/.chef/credentials") + allow(File).to receive(:expand_path).with("/home/you/.chef/#{Etc.getlogin}.pem").and_return("/home/you/.chef/#{Etc.getlogin}.pem") + allow(File).to receive(:expand_path).with(default_admin_key).and_return(default_admin_key) + expect(FileUtils).to receive(:mkdir_p).with("/home/you/.chef") + config_file = StringIO.new + expect(::File).to receive(:open).with("/home/you/.chef/credentials", "w").and_yield config_file + @knife.config[:repository] = "/home/you/chef-repo" + @knife.run + expect(config_file.string).to match(/^client_name\s+=\s+'#{Etc.getlogin}'$/) + expect(config_file.string).to match(%r{^client_key\s+=\s+'/home/you/.chef/#{Etc.getlogin}.pem'$}) + expect(config_file.string).to match(/^chef_server_url\s+=\s+'#{default_server_url}'$/) + end + + it "creates a new client when given the --initial option" do + allow(Chef::Util::PathHelper).to receive(:home).with(".chef").and_return("/home/you/.chef") + expect(File).to receive(:expand_path).with("/home/you/.chef/credentials").and_return("/home/you/.chef/credentials") + expect(File).to receive(:expand_path).with("/home/you/.chef/a-new-user.pem").and_return("/home/you/.chef/a-new-user.pem") + allow(File).to receive(:expand_path).with(default_admin_key).and_return(default_admin_key) + Chef::Config[:node_name] = "webmonkey.example.com" + + user_command = Chef::Knife::UserCreate.new + expect(user_command).to receive(:run) + + allow(Etc).to receive(:getlogin).and_return("a-new-user") + + allow(Chef::Knife::UserCreate).to receive(:new).and_return(user_command) + expect(FileUtils).to receive(:mkdir_p).with("/home/you/.chef") + expect(::File).to receive(:open).with("/home/you/.chef/credentials", "w") + @knife.config[:initial] = true + @knife.config[:user_password] = "blah" + @knife.run + expect(user_command.name_args).to eq(Array("a-new-user")) + expect(user_command.config[:user_password]).to eq("blah") + expect(user_command.config[:admin]).to be_truthy + expect(user_command.config[:file]).to eq("/home/you/.chef/a-new-user.pem") + expect(user_command.config[:yes]).to be_truthy + expect(user_command.config[:disable_editing]).to be_truthy + end +end diff --git a/knife/spec/unit/knife/cookbook_bulk_delete_spec.rb b/knife/spec/unit/knife/cookbook_bulk_delete_spec.rb new file mode 100644 index 0000000000..52f9c1eeb9 --- /dev/null +++ b/knife/spec/unit/knife/cookbook_bulk_delete_spec.rb @@ -0,0 +1,87 @@ +# +# Author:: Stephen Delano (<stephen@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" + +describe Chef::Knife::CookbookBulkDelete do + before(:each) do + Chef::Log.logger = Logger.new(StringIO.new) + + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::CookbookBulkDelete.new + @knife.config = { print_after: nil } + @knife.name_args = ["."] + @stdout = StringIO.new + @stderr = StringIO.new + allow(@knife.ui).to receive(:stdout).and_return(@stdout) + allow(@knife.ui).to receive(:stderr).and_return(@stderr) + allow(@knife.ui).to receive(:confirm).and_return(true) + @cookbooks = {} + %w{cheezburger pizza lasagna}.each do |cookbook_name| + cookbook = Chef::CookbookVersion.new(cookbook_name) + @cookbooks[cookbook_name] = cookbook + end + @rest = double("Chef::ServerAPI") + allow(@rest).to receive(:get).and_return(@cookbooks) + allow(@rest).to receive(:delete).and_return(true) + allow(@knife).to receive(:rest).and_return(@rest) + allow(Chef::CookbookVersion).to receive(:list).and_return(@cookbooks) + + end + + describe "when there are several cookbooks on the server" do + before do + @cheezburger = { "cheezburger" => { "url" => "file:///dev/null", "versions" => [{ "url" => "file:///dev/null-cheez", "version" => "1.0.0" }] } } + allow(@rest).to receive(:get).with("cookbooks/cheezburger").and_return(@cheezburger) + @pizza = { "pizza" => { "url" => "file:///dev/null", "versions" => [{ "url" => "file:///dev/null-pizza", "version" => "2.0.0" }] } } + allow(@rest).to receive(:get).with("cookbooks/pizza").and_return(@pizza) + @lasagna = { "lasagna" => { "url" => "file:///dev/null", "versions" => [{ "url" => "file:///dev/null-lasagna", "version" => "3.0.0" }] } } + allow(@rest).to receive(:get).with("cookbooks/lasagna").and_return(@lasagna) + end + + it "should print the cookbooks you are about to delete" do + expected = @knife.ui.list(@cookbooks.keys.sort, :columns_down) + @knife.run + expect(@stdout.string).to match(/#{expected}/) + end + + it "should confirm you really want to delete them" do + expect(@knife.ui).to receive(:confirm) + @knife.run + end + + it "should delete each cookbook" do + { "cheezburger" => "1.0.0", "pizza" => "2.0.0", "lasagna" => "3.0.0" }.each do |cookbook_name, version| + expect(@rest).to receive(:delete).with("cookbooks/#{cookbook_name}/#{version}") + end + @knife.run + end + + it "should only delete cookbooks that match the regex" do + @knife.name_args = ["cheezburger"] + expect(@rest).to receive(:delete).with("cookbooks/cheezburger/1.0.0") + @knife.run + end + end + + it "should exit if the regex is not provided" do + @knife.name_args = [] + expect { @knife.run }.to raise_error(SystemExit) + end + +end diff --git a/knife/spec/unit/knife/cookbook_delete_spec.rb b/knife/spec/unit/knife/cookbook_delete_spec.rb new file mode 100644 index 0000000000..b05006f2d3 --- /dev/null +++ b/knife/spec/unit/knife/cookbook_delete_spec.rb @@ -0,0 +1,239 @@ +# +# Author:: Thomas Bishop (<bishop.thomas@gmail.com>) +# Copyright:: Copyright 2011-2016, Thomas Bishop +# 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" + +describe Chef::Knife::CookbookDelete do + before(:each) do + @knife = Chef::Knife::CookbookDelete.new + @knife.name_args = ["foobar"] + @knife.cookbook_name = "foobar" + @stdout = StringIO.new + allow(@knife.ui).to receive(:stdout).and_return(@stdout) + @stderr = StringIO.new + allow(@knife.ui).to receive(:stderr).and_return(@stderr) + end + + describe "run" do + it "should print usage and exit when a cookbook name is not provided" do + @knife.name_args = [] + expect(@knife).to receive(:show_usage) + expect(@knife.ui).to receive(:fatal) + expect { @knife.run }.to raise_error(SystemExit) + end + + describe "when specifying a cookbook name" do + it "should delete the cookbook without a specific version" do + expect(@knife).to receive(:delete_without_explicit_version) + @knife.run + end + + describe "and a version" do + it "should delete the specific version of the cookbook" do + @knife.name_args << "1.0.0" + expect(@knife).to receive(:delete_explicit_version) + @knife.run + end + end + + describe "with -a or --all" do + it "should delete all versions of the cookbook" do + @knife.config[:all] = true + expect(@knife).to receive(:delete_all_versions) + @knife.run + end + end + + describe "with -p or --purge" do + it "should prompt to purge the files" do + @knife.config[:purge] = true + expect(@knife).to receive(:confirm) + .with(/.+Are you sure you want to purge files.+/) + expect(@knife).to receive(:delete_without_explicit_version) + @knife.run + end + end + end + end + + describe "delete_explicit_version" do + it "should delete the specific cookbook version" do + @knife.cookbook_name = "foobar" + @knife.version = "1.0.0" + expect(@knife).to receive(:delete_object).with(Chef::CookbookVersion, + "foobar version 1.0.0", + "cookbook").and_yield + expect(@knife).to receive(:delete_request).with("cookbooks/foobar/1.0.0") + @knife.delete_explicit_version + end + end + + describe "delete_all_versions" do + it "should prompt to delete all versions of the cookbook" do + @knife.cookbook_name = "foobar" + expect(@knife).to receive(:confirm).with("Do you really want to delete all versions of foobar") + expect(@knife).to receive(:delete_all_without_confirmation) + @knife.delete_all_versions + end + end + + describe "delete_all_without_confirmation" do + it "should delete all versions without confirmation" do + versions = ["1.0.0", "1.1.0"] + expect(@knife).to receive(:available_versions).and_return(versions) + versions.each do |v| + expect(@knife).to receive(:delete_version_without_confirmation).with(v) + end + @knife.delete_all_without_confirmation + end + end + + describe "delete_without_explicit_version" do + it "should exit if there are no available versions" do + expect(@knife).to receive(:available_versions).and_return(nil) + expect { @knife.delete_without_explicit_version }.to raise_error(SystemExit) + end + + it "should delete the version if only one is found" do + expect(@knife).to receive(:available_versions).at_least(:once).and_return(["1.0.0"]) + expect(@knife).to receive(:delete_explicit_version) + @knife.delete_without_explicit_version + end + + it "should ask which version(s) to delete if multiple are found" do + expect(@knife).to receive(:available_versions).at_least(:once).and_return(["1.0.0", "1.1.0"]) + expect(@knife).to receive(:ask_which_versions_to_delete).and_return(["1.0.0", "1.1.0"]) + expect(@knife).to receive(:delete_versions_without_confirmation).with(["1.0.0", "1.1.0"]) + @knife.delete_without_explicit_version + end + end + + describe "available_versions" do + before(:each) do + @rest_mock = double("rest") + expect(@knife).to receive(:rest).and_return(@rest_mock) + @cookbook_data = { "foobar" => { "versions" => [{ "version" => "1.0.0" }, + { "version" => "1.1.0" }, + { "version" => "2.0.0" } ] }, + } + end + + it "should return the list of versions of the cookbook" do + expect(@rest_mock).to receive(:get).with("cookbooks/foobar").and_return(@cookbook_data) + expect(@knife.available_versions).to eq(["1.0.0", "1.1.0", "2.0.0"]) + end + + it "should raise if an error other than HTTP 404 is returned" do + exception = Net::HTTPClientException.new("500 Internal Server Error", "500") + expect(@rest_mock).to receive(:get).and_raise(exception) + expect { @knife.available_versions }.to raise_error Net::HTTPClientException + end + + describe "if the cookbook can't be found" do + before(:each) do + expect(@rest_mock).to receive(:get) + .and_raise(Net::HTTPClientException.new("404 Not Found", "404")) + end + + it "should print an error" do + @knife.available_versions + expect(@stderr.string).to match(/error.+cannot find a cookbook named foobar/i) + end + + it "should return nil" do + expect(@knife.available_versions).to eq(nil) + end + end + end + + describe "ask_which_version_to_delete" do + before(:each) do + allow(@knife).to receive(:available_versions).and_return(["1.0.0", "1.1.0", "2.0.0"]) + end + + it "should prompt the user to select a version" do + prompt = /Which version\(s\) do you want to delete\?.+1\. foobar 1\.0\.0.+2\. foobar 1\.1\.0.+3\. foobar 2\.0\.0.+4\. All versions.+/m + expect(@knife).to receive(:ask_question).with(prompt).and_return("1") + @knife.ask_which_versions_to_delete + end + + it "should print an error and exit if a version wasn't specified" do + expect(@knife).to receive(:ask_question).and_return("") + expect(@knife.ui).to receive(:error).with(/no versions specified/i) + expect { @knife.ask_which_versions_to_delete }.to raise_error(SystemExit) + end + + it "should print an error if an invalid choice was selected" do + expect(@knife).to receive(:ask_question).and_return("100") + expect(@knife.ui).to receive(:error).with(/100 is not a valid choice/i) + @knife.ask_which_versions_to_delete + end + + it "should return the selected versions" do + expect(@knife).to receive(:ask_question).and_return("1, 3") + expect(@knife.ask_which_versions_to_delete).to eq(["1.0.0", "2.0.0"]) + end + + it "should return all of the versions if 'all' was selected" do + expect(@knife).to receive(:ask_question).and_return("4") + expect(@knife.ask_which_versions_to_delete).to eq([:all]) + end + end + + describe "delete_version_without_confirmation" do + it "should delete the cookbook version" do + expect(@knife).to receive(:delete_request).with("cookbooks/foobar/1.0.0") + @knife.delete_version_without_confirmation("1.0.0") + end + + it "should output that the cookbook was deleted" do + allow(@knife).to receive(:delete_request) + @knife.delete_version_without_confirmation("1.0.0") + expect(@stderr.string).to match(/deleted cookbook\[foobar\]\[1.0.0\]/im) + end + + describe "with --print-after" do + it "should display the cookbook data" do + object = "" + @knife.config[:print_after] = true + allow(@knife).to receive(:delete_request).and_return(object) + expect(@knife).to receive(:format_for_display).with(object) + @knife.delete_version_without_confirmation("1.0.0") + end + end + end + + describe "delete_versions_without_confirmation" do + it "should delete each version without confirmation" do + versions = ["1.0.0", "1.1.0"] + versions.each do |v| + expect(@knife).to receive(:delete_version_without_confirmation).with(v) + end + @knife.delete_versions_without_confirmation(versions) + end + + describe "with -a or --all" do + it "should delete all versions without confirmation" do + versions = [:all] + expect(@knife).to receive(:delete_all_without_confirmation) + @knife.delete_versions_without_confirmation(versions) + end + end + end + +end diff --git a/knife/spec/unit/knife/cookbook_download_spec.rb b/knife/spec/unit/knife/cookbook_download_spec.rb new file mode 100644 index 0000000000..b3dbc81205 --- /dev/null +++ b/knife/spec/unit/knife/cookbook_download_spec.rb @@ -0,0 +1,255 @@ +# +# Author:: Thomas Bishop (<bishop.thomas@gmail.com>) +# Copyright:: Copyright 2011-2016, Thomas Bishop +# 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" + +describe Chef::Knife::CookbookDownload do + before(:each) do + @knife = Chef::Knife::CookbookDownload.new + @stderr = StringIO.new + allow(@knife.ui).to receive(:stderr).and_return(@stderr) + end + + describe "run" do + it "should print usage and exit when a cookbook name is not provided" do + @knife.name_args = [] + expect(@knife).to receive(:show_usage) + expect(@knife.ui).to receive(:fatal).with(/must specify a cookbook name/) + expect { @knife.run }.to raise_error(SystemExit) + end + + it "should exit with a fatal error when there is no cookbook on the server" do + @knife.name_args = ["foobar", nil] + expect(@knife).to receive(:determine_version).and_return(nil) + expect(@knife.ui).to receive(:fatal).with("No such cookbook found") + expect { @knife.run }.to raise_error(SystemExit) + end + + describe "with a cookbook name" do + before(:each) do + @knife.name_args = ["foobar"] + @knife.config[:download_directory] = "/var/tmp/chef" + @rest_mock = double("rest") + allow(@knife).to receive(:rest).and_return(@rest_mock) + + expect(Chef::CookbookVersion).to receive(:load).with("foobar", "1.0.0") + .and_return(cookbook) + end + + let(:manifest_data) do + { + all_files: [ + { + "path" => "recipes/foo.rb", + "name" => "recipes/foo.rb", + "url" => "http://example.org/files/foo.rb", + }, + { + "path" => "recipes/bar.rb", + "name" => "recipes/bar.rb", + "url" => "http://example.org/files/bar.rb", + }, + { + "path" => "templates/default/foo.erb", + "name" => "templates/foo.erb", + "url" => "http://example.org/files/foo.erb", + }, + { + "path" => "templates/default/bar.erb", + "name" => "templates/bar.erb", + "url" => "http://example.org/files/bar.erb", + }, + { + "path" => "attributes/default.rb", + "name" => "attributes/default.rb", + "url" => "http://example.org/files/default.rb", + }, + ], + } + end + + let(:cookbook) do + cb = Chef::CookbookVersion.new("foobar") + cb.version = "1.0.0" + cb.manifest = manifest_data + cb + end + + describe "and no version" do + let(:manifest_data) { { all_files: [] } } + it "should determine which version to download" do + expect(@knife).to receive(:determine_version).and_return("1.0.0") + expect(File).to receive(:exist?).with("/var/tmp/chef/foobar-1.0.0").and_return(false) + @knife.run + end + end + + describe "and a version" do + before(:each) do + @knife.name_args << "1.0.0" + @files = manifest_data.values.map { |v| v.map { |i| i["path"] } }.flatten.uniq + @files_mocks = {} + @files.map { |f| File.basename(f) }.flatten.uniq.each do |f| + @files_mocks[f] = double("#{f}_mock") + allow(@files_mocks[f]).to receive(:path).and_return("/var/tmp/#{f}") + end + end + + it "should print an error and exit if the cookbook download directory already exists" do + expect(File).to receive(:exist?).with("/var/tmp/chef/foobar-1.0.0").and_return(true) + expect(@knife.ui).to receive(:fatal).with(%r{/var/tmp/chef/foobar-1\.0\.0 exists}i) + expect { @knife.run }.to raise_error(SystemExit) + end + + describe "when downloading the cookbook" do + before(:each) do + @files.map { |f| File.dirname(f) }.flatten.uniq.each do |dir| + expect(FileUtils).to receive(:mkdir_p).with("/var/tmp/chef/foobar-1.0.0/#{dir}") + .at_least(:once) + end + + @files_mocks.each_pair do |file, mock| + expect(@rest_mock).to receive(:streaming_request).with("http://example.org/files/#{file}") + .and_return(mock) + end + + @files.each do |f| + expect(FileUtils).to receive(:mv) + .with("/var/tmp/#{File.basename(f)}", "/var/tmp/chef/foobar-1.0.0/#{f}") + end + end + + it "should download the cookbook when the cookbook download directory doesn't exist" do + expect(File).to receive(:exist?).with("/var/tmp/chef/foobar-1.0.0").and_return(false) + @knife.run + %w{attributes recipes templates}.each do |segment| + expect(@stderr.string).to match(/downloading #{segment}/im) + end + expect(@stderr.string).to match(/downloading foobar cookbook version 1\.0\.0/im) + expect(@stderr.string).to match %r{cookbook downloaded to /var/tmp/chef/foobar-1\.0\.0}im + end + + describe "with -f or --force" do + it "should remove the existing the cookbook download directory if it exists" do + @knife.config[:force] = true + expect(File).to receive(:exist?).with("/var/tmp/chef/foobar-1.0.0").and_return(true) + expect(FileUtils).to receive(:rm_rf).with("/var/tmp/chef/foobar-1.0.0") + @knife.run + end + end + end + + end + end + + end + + describe "determine_version" do + + it "should return nil if there are no versions" do + expect(@knife).to receive(:available_versions).and_return(nil) + expect(@knife.determine_version).to eq(nil) + expect(@knife.version).to eq(nil) + end + + it "should return and set the version if there is only one version" do + expect(@knife).to receive(:available_versions).at_least(:once).and_return(["1.0.0"]) + expect(@knife.determine_version).to eq("1.0.0") + expect(@knife.version).to eq("1.0.0") + end + + it "should ask which version to download and return it if there is more than one" do + expect(@knife).to receive(:available_versions).at_least(:once).and_return(["1.0.0", "2.0.0"]) + expect(@knife).to receive(:ask_which_version).and_return("1.0.0") + expect(@knife.determine_version).to eq("1.0.0") + end + + describe "with -N or --latest" do + it "should return and set the version to the latest version" do + @knife.config[:latest] = true + expect(@knife).to receive(:available_versions).at_least(:once) + .and_return(["1.0.0", "1.1.0", "2.0.0"]) + @knife.determine_version + expect(@knife.version.to_s).to eq("2.0.0") + end + end + end + + describe "available_versions" do + before(:each) do + @knife.cookbook_name = "foobar" + end + + it "should return nil if there are no versions" do + expect(Chef::CookbookVersion).to receive(:available_versions) + .with("foobar") + .and_return(nil) + expect(@knife.available_versions).to eq(nil) + end + + it "should return the available versions" do + expect(Chef::CookbookVersion).to receive(:available_versions) + .with("foobar") + .and_return(["1.1.0", "2.0.0", "1.0.0"]) + expect(@knife.available_versions).to eq([Chef::Version.new("1.0.0"), + Chef::Version.new("1.1.0"), + Chef::Version.new("2.0.0")]) + end + + it "should avoid multiple API calls to the server" do + expect(Chef::CookbookVersion).to receive(:available_versions) + .once + .with("foobar") + .and_return(["1.1.0", "2.0.0", "1.0.0"]) + @knife.available_versions + @knife.available_versions + end + end + + describe "ask_which_version" do + before(:each) do + @knife.cookbook_name = "foobar" + allow(@knife).to receive(:available_versions).and_return(["1.0.0", "1.1.0", "2.0.0"]) + end + + it "should prompt the user to select a version" do + prompt = /Which version do you want to download\?.+1\. foobar 1\.0\.0.+2\. foobar 1\.1\.0.+3\. foobar 2\.0\.0.+/m + expect(@knife).to receive(:ask_question).with(prompt).and_return("1") + @knife.ask_which_version + end + + it "should set the version to the user's selection" do + expect(@knife).to receive(:ask_question).and_return("1") + @knife.ask_which_version + expect(@knife.version).to eq("1.0.0") + end + + it "should print an error and exit if a version wasn't specified" do + expect(@knife).to receive(:ask_question).and_return("") + expect(@knife.ui).to receive(:error).with(/is not a valid value/i) + expect { @knife.ask_which_version }.to raise_error(SystemExit) + end + + it "should print an error if an invalid choice was selected" do + expect(@knife).to receive(:ask_question).and_return("100") + expect(@knife.ui).to receive(:error).with(/'100' is not a valid value/i) + expect { @knife.ask_which_version }.to raise_error(SystemExit) + end + end + +end diff --git a/knife/spec/unit/knife/cookbook_list_spec.rb b/knife/spec/unit/knife/cookbook_list_spec.rb new file mode 100644 index 0000000000..42c3ef1bfd --- /dev/null +++ b/knife/spec/unit/knife/cookbook_list_spec.rb @@ -0,0 +1,88 @@ +# +# Author:: Thomas Bishop (<bishop.thomas@gmail.com>) +# Copyright:: Copyright 2011-2016, Thomas Bishop +# 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" + +describe Chef::Knife::CookbookList do + before do + @knife = Chef::Knife::CookbookList.new + @rest_mock = double("rest") + allow(@knife).to receive(:rest).and_return(@rest_mock) + @cookbook_names = %w{apache2 mysql} + @base_url = "https://server.example.com/cookbooks" + @cookbook_data = {} + @cookbook_names.each do |item| + @cookbook_data[item] = { "url" => "#{@base_url}/#{item}", + "versions" => [{ "version" => "1.0.1", + "url" => "#{@base_url}/#{item}/1.0.1" }] } + end + @stdout = StringIO.new + allow(@knife.ui).to receive(:stdout).and_return(@stdout) + end + + describe "run" do + it "should display the latest version of the cookbooks" do + expect(@rest_mock).to receive(:get).with("/cookbooks?num_versions=1") + .and_return(@cookbook_data) + @knife.run + @cookbook_names.each do |item| + expect(@stdout.string).to match(/#{item}\s+1\.0\.1/) + end + end + + it "should query cookbooks for the configured environment" do + @knife.config[:environment] = "production" + expect(@rest_mock).to receive(:get) + .with("/environments/production/cookbooks?num_versions=1") + .and_return(@cookbook_data) + @knife.run + end + + describe "with -w or --with-uri" do + it "should display the cookbook uris" do + @knife.config[:with_uri] = true + allow(@rest_mock).to receive(:get).and_return(@cookbook_data) + @knife.run + @cookbook_names.each do |item| + pattern = /#{Regexp.escape(@cookbook_data[item]['versions'].first['url'])}/ + expect(@stdout.string).to match pattern + end + end + end + + describe "with -a or --all" do + before do + @cookbook_names.each do |item| + @cookbook_data[item]["versions"] << { "version" => "1.0.0", + "url" => "#{@base_url}/#{item}/1.0.0" } + end + end + + it "should display all versions of the cookbooks" do + @knife.config[:all_versions] = true + expect(@rest_mock).to receive(:get).with("/cookbooks?num_versions=all") + .and_return(@cookbook_data) + @knife.run + @cookbook_names.each do |item| + expect(@stdout.string).to match(/#{item}\s+1\.0\.1\s+1\.0\.0/) + end + end + end + + end +end diff --git a/knife/spec/unit/knife/cookbook_metadata_from_file_spec.rb b/knife/spec/unit/knife/cookbook_metadata_from_file_spec.rb new file mode 100644 index 0000000000..c595aef96f --- /dev/null +++ b/knife/spec/unit/knife/cookbook_metadata_from_file_spec.rb @@ -0,0 +1,72 @@ +# +# Author:: Adam Jacob (<adam@chef.io>) +# Author:: Matthew Kent (<mkent@magoazul.com>) +# Copyright:: Copyright (c) Chef Software Inc. +# 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" + +describe Chef::Knife::CookbookMetadataFromFile do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @src = File.expand_path(File.join(CHEF_SPEC_DATA, "metadata", "quick_start", "metadata.rb")) + @tgt = File.expand_path(File.join(CHEF_SPEC_DATA, "metadata", "quick_start", "metadata.json")) + @knife = Chef::Knife::CookbookMetadataFromFile.new + @knife.name_args = [ @src ] + allow(@knife).to receive(:to_json_pretty).and_return(true) + @md = Chef::Cookbook::Metadata.new + allow(Chef::Cookbook::Metadata).to receive(:new).and_return(@md) + allow($stdout).to receive(:write) + end + + after do + if File.exist?(@tgt) + File.unlink(@tgt) + end + end + + describe "run" do + it "should print usage and exit when a FILE is not provided" do + @knife.name_args = [] + expect(@knife).to receive(:show_usage) + expect(@knife.ui).to receive(:fatal).with(/You must specify the FILE./) + expect { @knife.run }.to raise_error(SystemExit) + end + + it "should determine cookbook name from path" do + expect(@md).to receive(:name).with(no_args) + expect(@md).to receive(:name).with("quick_start") + @knife.run + end + + it "should load the metadata source" do + expect(@md).to receive(:from_file).with(@src) + @knife.run + end + + it "should write out the metadata to the correct location" do + expect(File).to receive(:open).with(@tgt, "w") + @knife.run + end + + it "should generate json from the metadata" do + expect(Chef::JSONCompat).to receive(:to_json_pretty).with(@md) + @knife.run + end + + end +end diff --git a/knife/spec/unit/knife/cookbook_metadata_spec.rb b/knife/spec/unit/knife/cookbook_metadata_spec.rb new file mode 100644 index 0000000000..1a274cc6f4 --- /dev/null +++ b/knife/spec/unit/knife/cookbook_metadata_spec.rb @@ -0,0 +1,182 @@ +# +# Author:: Thomas Bishop (<bishop.thomas@gmail.com>) +# Copyright:: Copyright 2011-2016, Thomas Bishop +# 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" + +describe Chef::Knife::CookbookMetadata do + let(:knife) do + knife = Chef::Knife::CookbookMetadata.new + knife.name_args = ["foobar"] + knife + end + + let(:cookbook_dir) { Dir.mktmpdir } + + let(:stdout) { StringIO.new } + + let(:stderr) { StringIO.new } + + before(:each) do + allow(knife.ui).to receive(:stdout).and_return(stdout) + allow(knife.ui).to receive(:stderr).and_return(stderr) + end + + def create_metadata_rb(**kwargs) + name = kwargs[:name] + Dir.mkdir("#{cookbook_dir}/#{name}") + File.open("#{cookbook_dir}/#{name}/metadata.rb", "w+") do |f| + kwargs.each do |key, value| + if value.is_a?(Array) + f.puts "#{key} #{value.map { |v| "\"#{v}\"" }.join(", ")}" + else + f.puts "#{key} \"#{value}\"" + end + end + end + end + + def create_metadata_json(**kwargs) + name = kwargs[:name] + Dir.mkdir("#{cookbook_dir}/#{name}") + File.open("#{cookbook_dir}/#{name}/metadata.json", "w+") do |f| + f.write(FFI_Yajl::Encoder.encode(kwargs)) + end + end + + def create_invalid_json + Dir.mkdir("#{cookbook_dir}/foobar") + File.open("#{cookbook_dir}/foobar/metadata.json", "w+") do |f| + f.write <<-EOH + { "version": "1.0.0", {ImInvalid}} + EOH + end + end + + describe "run" do + it "should print an error and exit if a cookbook name was not provided" do + knife.name_args = [] + expect(knife.ui).to receive(:error).with(/you must specify the cookbook.+use the --all/i) + expect { knife.run }.to raise_error(SystemExit) + end + + it "should print an error and exit if an empty cookbook name was provided" do + knife.name_args = [""] + expect(knife.ui).to receive(:error).with(/you must specify the cookbook.+use the --all/i) + expect { knife.run }.to raise_error(SystemExit) + end + + it "should generate the metadata for the cookbook" do + expect(knife).to receive(:generate_metadata).with("foobar") + knife.run + end + + describe "with -a or --all" do + before(:each) do + Chef::Config[:cookbook_path] = cookbook_dir + knife.config[:all] = true + create_metadata_rb(name: "foo", version: "1.0.0") + create_metadata_rb(name: "bar", version: "2.0.0") + expect(knife).to receive(:generate_metadata).with("foo").and_call_original + expect(knife).to receive(:generate_metadata).with("bar").and_call_original + end + + it "should generate the metadata for each cookbook" do + expect(Chef::CookbookLoader).to receive(:new).with(cookbook_dir).and_call_original + knife.run + expect(stderr.string).to match %r{generating metadata for foo from #{cookbook_dir}/foo/metadata\.rb}im + expect(stderr.string).to match %r{generating metadata for bar from #{cookbook_dir}/bar/metadata\.rb}im + end + + it "with -o or --cookbook_path should look in the provided path and generate cookbook metadata" do + Chef::Config[:cookbook_path] = "/dev/null" + knife.config[:cookbook_path] = cookbook_dir + expect(Chef::CookbookLoader).to receive(:new).with(cookbook_dir).and_call_original + knife.run + expect(stderr.string).to match %r{generating metadata for foo from #{cookbook_dir}/foo/metadata\.rb}im + expect(stderr.string).to match %r{generating metadata for bar from #{cookbook_dir}/bar/metadata\.rb}im + end + end + + end + + describe "generate_metadata" do + before(:each) do + Chef::Config[:cookbook_path] = cookbook_dir + end + + it "should generate the metadata from metadata.rb if it exists" do + create_metadata_rb(name: "foobar", version: "1.0.0") + expect(knife).to receive(:generate_metadata_from_file).with("foobar", "#{cookbook_dir}/foobar/metadata.rb").and_call_original + knife.run + expect(File.exist?("#{cookbook_dir}/foobar/metadata.json")).to be true + json = FFI_Yajl::Parser.parse(IO.read("#{cookbook_dir}/foobar/metadata.json")) + expect(json["name"]).to eql("foobar") + expect(json["version"]).to eql("1.0.0") + end + + it "should validate the metadata json if metadata.rb does not exist" do + create_metadata_json(name: "foobar", version: "1.0.0") + expect(knife).to receive(:validate_metadata_json).with(cookbook_dir, "foobar").and_call_original + knife.run + end + end + + describe "validation errors" do + before(:each) do + Chef::Config[:cookbook_path] = cookbook_dir + end + + it "should fail for obsolete operators in metadata.rb" do + create_metadata_rb(name: "foobar", version: "1.0.0", depends: [ "foo:bar", ">> 0.2" ]) + expect(Chef::Cookbook::Metadata).not_to receive(:validate_json) + expect { knife.run }.to raise_error(SystemExit) + expect(stderr.string).to match(/error: the cookbook 'foobar' contains invalid or obsolete metadata syntax/im) + end + + it "should fail for obsolete format in metadata.rb (sadly)" do + create_metadata_rb(name: "foobar", version: "1.0.0", depends: [ "foo:bar", "> 0.2", "< 1.0" ]) + expect(Chef::Cookbook::Metadata).not_to receive(:validate_json) + expect { knife.run }.to raise_error(SystemExit) + expect(stderr.string).to match(/error: the cookbook 'foobar' contains invalid or obsolete metadata syntax/im) + end + + it "should fail for obsolete operators in metadata.json" do + create_metadata_json(name: "foobar", version: "1.0.0", dependencies: { "foo:bar" => ">> 0.2" }) + expect { knife.run }.to raise_error(SystemExit) + expect(stderr.string).to match(/error: the cookbook 'foobar' contains invalid or obsolete metadata syntax/im) + end + + it "should not fail for unknown field in metadata.rb" do + create_metadata_rb(name: "sounders", version: "2.0.0", beats: "toronto") + expect(Chef::Cookbook::Metadata).not_to receive(:validate_json) + expect { knife.run }.not_to raise_error + expect(stderr.string).to eql("") + end + + it "should not fail for unknown field in metadata.json" do + create_metadata_json(name: "sounders", version: "2.0.0", beats: "toronto") + expect { knife.run }.not_to raise_error + expect(stderr.string).to eql("") + end + + it "should fail on unparsable json" do + create_invalid_json + expect { knife.run }.to raise_error(Chef::Exceptions::JSON::ParseError) + end + end +end diff --git a/knife/spec/unit/knife/cookbook_show_spec.rb b/knife/spec/unit/knife/cookbook_show_spec.rb new file mode 100644 index 0000000000..94e080cb15 --- /dev/null +++ b/knife/spec/unit/knife/cookbook_show_spec.rb @@ -0,0 +1,253 @@ +# +# Author:: Adam Jacob (<adam@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" + +describe Chef::Knife::CookbookShow do + before do + Chef::Config[:node_name] = "webmonkey.example.com" + allow(knife).to receive(:rest).and_return(rest) + allow(knife).to receive(:pretty_print).and_return(true) + allow(knife).to receive(:output).and_return(true) + allow(Chef::CookbookVersion).to receive(:load).and_return(cb) + end + + let(:knife) do + knife = Chef::Knife::CookbookShow.new + knife.config = {} + knife.name_args = [ "cookbook_name" ] + knife + end + + let(:cb) do + cb = Chef::CookbookVersion.new("cookbook_name") + cb.manifest = manifest + cb + end + + let(:rest) { double(Chef::ServerAPI) } + + let(:content) { "Example recipe text" } + + let(:manifest) do + { + "all_files" => [ + { + name: "recipes/default.rb", + path: "recipes/default.rb", + checksum: "1234", + url: "http://example.org/files/default.rb", + }, + ], + } + end + + describe "run" do + describe "with 0 arguments: help" do + it "should should print usage and exit when given no arguments" do + knife.name_args = [] + expect(knife).to receive(:show_usage) + expect(knife.ui).to receive(:fatal) + expect { knife.run }.to raise_error(SystemExit) + end + end + + describe "with 1 argument: versions" do + let(:response) do + { + "cookbook_name" => { + "url" => "http://url/cookbooks/cookbook_name", + "versions" => [ + { "version" => "0.10.0", "url" => "http://url/cookbooks/cookbook_name/0.10.0" }, + { "version" => "0.9.0", "url" => "http://url/cookbookx/cookbook_name/0.9.0" }, + { "version" => "0.8.0", "url" => "http://url/cookbooks/cookbook_name/0.8.0" }, + ], + }, + } + end + + it "should show the raw cookbook data" do + expect(rest).to receive(:get).with("cookbooks/cookbook_name").and_return(response) + expect(knife).to receive(:format_cookbook_list_for_display).with(response) + knife.run + end + + it "should respect the user-supplied environment" do + knife.config[:environment] = "foo" + expect(rest).to receive(:get).with("environments/foo/cookbooks/cookbook_name").and_return(response) + expect(knife).to receive(:format_cookbook_list_for_display).with(response) + knife.run + end + end + + describe "with 2 arguments: name and version" do + before do + knife.name_args << "0.1.0" + end + + let(:output) do + { "cookbook_name" => "cookbook_name", + "name" => "cookbook_name-0.0.0", + "frozen?" => false, + "version" => "0.0.0", + "metadata" => { + "name" => nil, + "description" => "", + "eager_load_libraries" => true, + "long_description" => "", + "maintainer" => "", + "maintainer_email" => "", + "license" => "All rights reserved", + "platforms" => {}, + "dependencies" => {}, + "providing" => {}, + "recipes" => {}, + "version" => "0.0.0", + "source_url" => "", + "issues_url" => "", + "privacy" => false, + "chef_versions" => [], + "ohai_versions" => [], + "gems" => [], + }, + "recipes" => + [{ "name" => "recipes/default.rb", + "path" => "recipes/default.rb", + "checksum" => "1234", + "url" => "http://example.org/files/default.rb" }], + } + end + + it "should show the specific part of a cookbook" do + expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb) + expect(knife).to receive(:output).with(output) + knife.run + end + end + + describe "with 3 arguments: name, version, and segment" do + before(:each) do + knife.name_args = [ "cookbook_name", "0.1.0", "recipes" ] + end + + it "should print the json of the part" do + expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb) + expect(knife).to receive(:output).with(cb.files_for("recipes")) + knife.run + end + end + + describe "with 4 arguments: name, version, segment and filename" do + before(:each) do + knife.name_args = [ "cookbook_name", "0.1.0", "recipes", "default.rb" ] + end + + it "should print the raw result of the request (likely a file!)" do + expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb) + expect(rest).to receive(:streaming_request).with("http://example.org/files/default.rb").and_return(StringIO.new(content)) + expect(knife).to receive(:pretty_print).with(content) + knife.run + end + end + + describe "with 4 arguments: name, version, segment and filename -- with specificity" do + before(:each) do + knife.name_args = [ "cookbook_name", "0.1.0", "files", "afile.rb" ] + cb.manifest = { + "all_files" => [ + { + name: "files/afile.rb", + path: "files/host-examplehost.example.org/afile.rb", + checksum: "1111", + specificity: "host-examplehost.example.org", + url: "http://example.org/files/1111", + }, + { + name: "files/afile.rb", + path: "files/ubuntu-9.10/afile.rb", + checksum: "2222", + specificity: "ubuntu-9.10", + url: "http://example.org/files/2222", + }, + { + name: "files/afile.rb", + path: "files/ubuntu/afile.rb", + checksum: "3333", + specificity: "ubuntu", + url: "http://example.org/files/3333", + }, + { + name: "files/afile.rb", + path: "files/default/afile.rb", + checksum: "4444", + specificity: "default", + url: "http://example.org/files/4444", + }, + ], + } + + end + + describe "with --fqdn" do + it "should pass the fqdn" do + knife.config[:platform] = "example_platform" + knife.config[:platform_version] = "1.0" + knife.config[:fqdn] = "examplehost.example.org" + expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb) + expect(rest).to receive(:streaming_request).with("http://example.org/files/1111").and_return(StringIO.new(content)) + expect(knife).to receive(:pretty_print).with(content) + knife.run + end + end + + describe "and --platform" do + it "should pass the platform" do + knife.config[:platform] = "ubuntu" + knife.config[:platform_version] = "1.0" + knife.config[:fqdn] = "differenthost.example.org" + expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb) + expect(rest).to receive(:streaming_request).with("http://example.org/files/3333").and_return(StringIO.new(content)) + expect(knife).to receive(:pretty_print).with(content) + knife.run + end + end + + describe "and --platform-version" do + it "should pass the platform" do + knife.config[:platform] = "ubuntu" + knife.config[:platform_version] = "9.10" + knife.config[:fqdn] = "differenthost.example.org" + expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb) + expect(rest).to receive(:streaming_request).with("http://example.org/files/2222").and_return(StringIO.new(content)) + expect(knife).to receive(:pretty_print).with(content) + knife.run + end + end + + describe "with none of the arguments, it should use the default" do + it "should pass them all" do + expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb) + expect(rest).to receive(:streaming_request).with("http://example.org/files/4444").and_return(StringIO.new(content)) + expect(knife).to receive(:pretty_print).with(content) + knife.run + end + end + + end + end +end diff --git a/knife/spec/unit/knife/cookbook_upload_spec.rb b/knife/spec/unit/knife/cookbook_upload_spec.rb new file mode 100644 index 0000000000..0893f6a6b3 --- /dev/null +++ b/knife/spec/unit/knife/cookbook_upload_spec.rb @@ -0,0 +1,364 @@ +# +# Author:: Matthew Kent (<mkent@magoazul.com>) +# Author:: Steven Danna (<steve@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 "chef/cookbook_uploader" +require "timeout" + +describe Chef::Knife::CookbookUpload do + let(:cookbook) do + cookbook = Chef::CookbookVersion.new("test_cookbook", "/tmp/blah") + allow(cookbook).to receive(:has_metadata_file?).and_return(true) + allow(cookbook.metadata).to receive(:name).and_return(cookbook.name) + cookbook + end + + let(:cookbooks_by_name) do + { cookbook.name => cookbook } + end + + let(:cookbook_loader) do + cookbook_loader = cookbooks_by_name.dup + allow(cookbook_loader).to receive(:merged_cookbooks).and_return([]) + allow(cookbook_loader).to receive(:load_cookbooks).and_return(cookbook_loader) + allow(cookbook_loader).to receive(:compile_metadata).and_return(nil) + allow(cookbook_loader).to receive(:freeze_versions).and_return(nil) + cookbook_loader + end + + let(:cookbook_uploader) { double(upload_cookbooks: nil) } + + let(:output) { StringIO.new } + + let(:name_args) { ["test_cookbook"] } + + let(:knife) do + k = Chef::Knife::CookbookUpload.new + k.name_args = name_args + allow(k.ui).to receive(:stdout).and_return(output) + allow(k.ui).to receive(:stderr).and_return(output) + k + end + + before(:each) do + allow(Chef::CookbookLoader).to receive(:new).and_return(cookbook_loader) + allow(Chef::CookbookLoader).to receive(:copy_to_tmp_dir_from_array).and_yield(cookbook_loader) + end + + describe "with --concurrency" do + it "should upload cookbooks with predefined concurrency" do + allow(Chef::CookbookVersion).to receive(:list_all_versions).and_return({}) + knife.config[:concurrency] = 3 + test_cookbook = Chef::CookbookVersion.new("test_cookbook", "/tmp/blah") + allow(cookbook_loader).to receive(:each).and_yield("test_cookbook", test_cookbook) + allow(cookbook_loader).to receive(:cookbook_names).and_return(["test_cookbook"]) + expect(Chef::CookbookUploader).to receive(:new) + .with( kind_of(Array), { force: nil, concurrency: 3 }) + .and_return(double("Chef::CookbookUploader", upload_cookbooks: true)) + knife.run + end + end + + describe "run" do + before(:each) do + allow(Chef::CookbookUploader).to receive_messages(new: cookbook_uploader) + allow(Chef::CookbookVersion).to receive(:list_all_versions).and_return({}) + end + + it "should print usage and exit when a cookbook name is not provided" do + knife.name_args = [] + expect(knife).to receive(:show_usage) + expect(knife.ui).to receive(:fatal) + expect { knife.run }.to raise_error(SystemExit) + end + + describe "when specifying cookbook without metadata.rb or metadata.json" do + let(:name_args) { ["test_cookbook1"] } + let(:cookbook) do + cookbook = Chef::CookbookVersion.new("test_cookbook1", "/tmp/blah") + allow(cookbook).to receive(:has_metadata_file?).and_return(false) + cookbook + end + + it "should upload the cookbook" do + expect { knife.run }.to raise_error(Chef::Exceptions::MetadataNotFound) + end + end + + describe "when name attribute in metadata not set" do + let(:name_args) { ["test_cookbook1"] } + + let(:cookbook) do + cookbook = Chef::CookbookVersion.new("test_cookbook1", "/tmp/blah") + allow(cookbook).to receive(:has_metadata_file?).and_return(true) + allow(cookbook.metadata).to receive(:name).and_return(nil) + cookbook + end + + it "should upload the cookbook" do + expect { knife.run }.to raise_error(Chef::Exceptions::MetadataNotValid) + end + end + + describe "when specifying a cookbook name" do + it "should upload the cookbook" do + expect(knife).to receive(:upload).once + knife.run + end + + it "should report on success" do + expect(knife).to receive(:upload).once + expect(knife.ui).to receive(:info).with(/Uploaded 1 cookbook/) + knife.run + end + end + + describe "when specifying the same cookbook name twice" do + it "should upload the cookbook only once" do + knife.name_args = %w{test_cookbook test_cookbook} + expect(knife).to receive(:upload).once + knife.run + end + end + + describe "when specifying a cookbook name among many" do + let(:name_args) { ["test_cookbook1"] } + + let(:cookbook) do + cookbook = Chef::CookbookVersion.new("test_cookbook1", "/tmp/blah") + allow(cookbook).to receive(:has_metadata_file?).and_return(true) + allow(cookbook.metadata).to receive(:name).and_return(cookbook.name) + cookbook + end + + let(:cookbooks_by_name) do + { cookbook.name => cookbook } + end + + it "should read only one cookbook" do + expect(cookbook_loader).to receive(:[]).once.with("test_cookbook1").and_call_original + knife.run + end + + it "should not read all cookbooks" do + expect(cookbook_loader).to receive(:load_cookbooks) + knife.run + end + + it "should upload only one cookbook" do + expect(knife).to receive(:upload).exactly(1).times + knife.run + end + end + + # This is testing too much. We should break it up. + describe "when specifying a cookbook name with dependencies" do + let(:name_args) { ["test_cookbook2"] } + + let(:test_cookbook1) do + cookbook = Chef::CookbookVersion.new("test_cookbook1", "/tmp/blah") + allow(cookbook).to receive(:has_metadata_file?).and_return(true) + allow(cookbook.metadata).to receive(:name).and_return(cookbook.name) + cookbook + end + + let(:test_cookbook2) do + c = Chef::CookbookVersion.new("test_cookbook2") + c.metadata.depends("test_cookbook3") + allow(c).to receive(:has_metadata_file?).and_return(true) + allow(c.metadata).to receive(:name).and_return(c.name) + c + end + + let(:test_cookbook3) do + c = Chef::CookbookVersion.new("test_cookbook3") + c.metadata.depends("test_cookbook1") + c.metadata.depends("test_cookbook2") + allow(c).to receive(:has_metadata_file?).and_return(true) + allow(c.metadata).to receive(:name).and_return(c.name) + c + end + + let(:cookbooks_by_name) do + { "test_cookbook1" => test_cookbook1, + "test_cookbook2" => test_cookbook2, + "test_cookbook3" => test_cookbook3 } + end + + it "should upload all dependencies once" do + knife.config[:depends] = true + allow(knife).to receive(:cookbook_names).and_return(%w{test_cookbook1 test_cookbook2 test_cookbook3}) + expect(knife).to receive(:upload).exactly(3).times + expect do + Timeout.timeout(5) do + knife.run + end + end.not_to raise_error + end + end + + describe "when specifying a cookbook name with missing dependencies" do + let(:cookbook_dependency) { Chef::CookbookVersion.new("dependency", "/tmp/blah") } + + before(:each) do + cookbook.metadata.depends("dependency") + allow(cookbook_loader).to receive(:[]) do |ckbk| + { "test_cookbook" => cookbook, + "dependency" => cookbook_dependency }[ckbk] + end + allow(knife).to receive(:cookbook_names).and_return(%w{cookbook_dependency test_cookbook}) + @stdout, @stderr, @stdin = StringIO.new, StringIO.new, StringIO.new + knife.ui = Chef::Knife::UI.new(@stdout, @stderr, @stdin, {}) + end + + it "should exit and not upload the cookbook" do + expect(cookbook_loader).to receive(:[]).once.with("test_cookbook") + expect(cookbook_uploader).not_to receive(:upload_cookbooks) + expect { knife.run }.to raise_error(SystemExit) + end + + it "should output a message for a single missing dependency" do + expect { knife.run }.to raise_error(SystemExit) + expect(@stderr.string).to include("Cookbook test_cookbook depends on cookbooks which are not currently") + expect(@stderr.string).to include("being uploaded and cannot be found on the server.") + expect(@stderr.string).to include("The missing cookbook(s) are: 'dependency' version '>= 0.0.0'") + end + + it "should output a message for a multiple missing dependencies which are concatenated" do + cookbook_dependency2 = Chef::CookbookVersion.new("dependency2") + cookbook.metadata.depends("dependency2") + allow(cookbook_loader).to receive(:[]) do |ckbk| + { "test_cookbook" => cookbook, + "dependency" => cookbook_dependency, + "dependency2" => cookbook_dependency2 }[ckbk] + end + allow(knife).to receive(:cookbook_names).and_return(%w{dependency dependency2 test_cookbook}) + expect { knife.run }.to raise_error(SystemExit) + expect(@stderr.string).to include("Cookbook test_cookbook depends on cookbooks which are not currently") + expect(@stderr.string).to include("being uploaded and cannot be found on the server.") + expect(@stderr.string).to include("The missing cookbook(s) are:") + expect(@stderr.string).to include("'dependency' version '>= 0.0.0'") + expect(@stderr.string).to include("'dependency2' version '>= 0.0.0'") + end + end + + it "should freeze the version of the cookbooks if --freeze is specified" do + knife.config[:freeze] = true + expect(cookbook_loader).to receive(:freeze_versions).once + knife.run + end + + describe "with -a or --all" do + before(:each) do + knife.config[:all] = true + end + + context "when cookbooks exist in the cookbook path" do + let(:test_cookbook1) do + cookbook = Chef::CookbookVersion.new("test_cookbook1", "/tmp/blah") + allow(cookbook).to receive(:has_metadata_file?).and_return(true) + allow(cookbook.metadata).to receive(:name).and_return(cookbook.name) + cookbook + end + + let(:test_cookbook2) do + cookbook = Chef::CookbookVersion.new("test_cookbook2", "/tmp/blah") + allow(cookbook).to receive(:has_metadata_file?).and_return(true) + allow(cookbook.metadata).to receive(:name).and_return(cookbook.name) + cookbook + end + + before(:each) do + allow(cookbook_loader).to receive(:each).and_yield("test_cookbook1", test_cookbook1).and_yield("test_cookbook2", test_cookbook2) + allow(cookbook_loader).to receive(:cookbook_names).and_return(%w{test_cookbook1 test_cookbook2}) + end + + it "should upload all cookbooks" do + expect(knife).to receive(:upload).once + knife.run + end + + it "should report on success" do + expect(knife).to receive(:upload).once + expect(knife.ui).to receive(:info).with(/Uploaded all cookbooks/) + knife.run + end + + it "should update the version constraints for an environment" do + allow(knife).to receive(:assert_environment_valid!).and_return(true) + knife.config[:environment] = "production" + expect(knife).to receive(:update_version_constraints).once + knife.run + end + end + + context "when no cookbooks exist in the cookbook path" do + before(:each) do + allow(cookbook_loader).to receive(:each) + end + + it "should not upload any cookbooks" do + expect(knife).to_not receive(:upload) + knife.run + end + + context "when cookbook path is an array" do + it "should warn users that no cookbooks exist" do + cookbook_path = windows? ? "C:/chef-repo/cookbooks" : "/chef-repo/cookbooks" + knife.config[:cookbook_path] = [cookbook_path, "/home/user/cookbooks"] + expect(knife.ui).to receive(:warn).with("Could not find any cookbooks in your cookbook path: '#{knife.config[:cookbook_path].join(", ")}'. Use --cookbook-path to specify the desired path.") + knife.run + end + end + + context "when cookbook path is a string" do + it "should warn users that no cookbooks exist" do + knife.config[:cookbook_path] = windows? ? "C:/chef-repo/cookbooks" : "/chef-repo/cookbooks" + expect(knife.ui).to receive(:warn).with( + "Could not find any cookbooks in your cookbook path: '#{knife.config[:cookbook_path]}'. Use --cookbook-path to specify the desired path." + ) + knife.run + end + end + end + end + + describe "when a frozen cookbook exists on the server" do + it "should fail to replace it" do + exception = Chef::Exceptions::CookbookFrozen.new + expect(cookbook_uploader).to receive(:upload_cookbooks) + .and_raise(exception) + allow(knife.ui).to receive(:error) + expect(knife.ui).to receive(:error).with(exception) + expect { knife.run }.to raise_error(SystemExit) + end + + it "should not update the version constraints for an environment" do + allow(knife).to receive(:assert_environment_valid!).and_return(true) + knife.config[:environment] = "production" + allow(knife).to receive(:upload).and_raise(Chef::Exceptions::CookbookFrozen) + expect(knife.ui).to receive(:error).with(/Failed to upload 1 cookbook/) + expect(knife.ui).to receive(:warn).with(/Not updating version constraints/) + expect(knife).not_to receive(:update_version_constraints) + expect { knife.run }.to raise_error(SystemExit) + end + end + end # run +end diff --git a/knife/spec/unit/knife/core/bootstrap_context_spec.rb b/knife/spec/unit/knife/core/bootstrap_context_spec.rb new file mode 100644 index 0000000000..79fddc8184 --- /dev/null +++ b/knife/spec/unit/knife/core/bootstrap_context_spec.rb @@ -0,0 +1,287 @@ +# +# Author:: Daniel DeLeo (<dan@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 "chef/knife/core/bootstrap_context" + +describe Chef::Knife::Core::BootstrapContext do + let(:config) { { foo: :bar, color: true } } + let(:run_list) { Chef::RunList.new("recipe[tmux]", "role[base]") } + let(:chef_config) do + { + config_log_level: "info", + config_log_location: "/tmp/log", + validation_key: File.join(CHEF_SPEC_DATA, "ssl", "private_key.pem"), + chef_server_url: "http://chef.example.com:4444", + validation_client_name: "chef-validator-testing", + } + end + + let(:secret) { nil } + + subject(:bootstrap_context) { described_class.new(config, run_list, chef_config, secret) } + + it "initializes with Chef 11 parameters" do + expect { described_class.new(config, run_list, chef_config) }.not_to raise_error + end + + it "runs chef with the first-boot.json with no environment" do + expect(bootstrap_context.start_chef).to eq "chef-client -j /etc/chef/first-boot.json" + end + + describe "when in verbosity mode" do + let(:config) { { verbosity: 2, color: true } } + it "adds '-l debug' when verbosity is >= 2" do + expect(bootstrap_context.start_chef).to eq "chef-client -j /etc/chef/first-boot.json -l debug" + end + end + + describe "when no color value has been set in config" do + let(:config) { { color: false } } + it "adds '--no-color' when color is false" do + expect(bootstrap_context.start_chef).to eq "chef-client -j /etc/chef/first-boot.json --no-color" + end + end + + it "reads the validation key" do + expect(bootstrap_context.validation_key).to eq IO.read(File.join(CHEF_SPEC_DATA, "ssl", "private_key.pem")) + end + + it "generates the config file data" do + expected = <<~EXPECTED + chef_server_url "http://chef.example.com:4444" + validation_client_name "chef-validator-testing" + log_level :info + log_location "/tmp/log" + # Using default node name (fqdn) + EXPECTED + expect(bootstrap_context.config_content).to eq expected + end + + describe "when chef_license is set" do + let(:chef_config) { { chef_license: "accept-no-persist" } } + it "sets chef_license in the generated config file" do + expect(bootstrap_context.config_content).to include("chef_license \"accept-no-persist\"") + end + end + + describe "when file_cache_path is set" do + let(:chef_config) { { file_cache_path: "/home/opscode/cache" } } + it "sets file_cache_path in the generated config file" do + expect(bootstrap_context.config_content).to include("file_cache_path \"/home/opscode/cache\"") + end + end + + describe "when file_backup_path is set" do + let(:chef_config) { { file_backup_path: "/home/opscode/backup" } } + it "sets file_backup_path in the generated config file" do + expect(bootstrap_context.config_content).to include("file_backup_path \"/home/opscode/backup\"") + end + end + + describe "alternate chef-client path" do + let(:chef_config) { { chef_client_path: "/usr/local/bin/chef-client" } } + it "runs chef-client from another path when specified" do + expect(bootstrap_context.start_chef).to eq "/usr/local/bin/chef-client -j /etc/chef/first-boot.json" + end + end + + describe "validation key path that contains a ~" do + let(:chef_config) { { validation_key: "~/my.key" } } + it "reads the validation key when it contains a ~" do + expect(File).to receive(:exist?).with(File.expand_path("my.key", ENV["HOME"])).and_return(true) + expect(IO).to receive(:read).with(File.expand_path("my.key", ENV["HOME"])) + bootstrap_context.validation_key + end + end + + describe "when an explicit node name is given" do + let(:config) { { chef_node_name: "foobar.example.com" } } + it "sets the node name in the client.rb" do + expect(bootstrap_context.config_content).to match(/node_name "foobar\.example\.com"/) + end + end + + describe "when bootstrapping into a specific environment" do + let(:config) { { environment: "prodtastic", color: true } } + it "starts chef in the configured environment" do + expect(bootstrap_context.start_chef).to eq("chef-client -j /etc/chef/first-boot.json -E prodtastic") + end + end + + describe "when tags are given" do + let(:config) { { tags: [ "unicorn" ] } } + it "adds the attributes to first_boot" do + expect(Chef::JSONCompat.to_json(bootstrap_context.first_boot)).to eq(Chef::JSONCompat.to_json({ run_list: run_list, tags: ["unicorn"] })) + end + end + + describe "when JSON attributes are given" do + let(:config) { { first_boot_attributes: { baz: :quux } } } + it "adds the attributes to first_boot" do + expect(Chef::JSONCompat.to_json(bootstrap_context.first_boot)).to eq(Chef::JSONCompat.to_json({ baz: :quux, run_list: run_list })) + end + end + + describe "when JSON attributes are NOT given" do + it "sets first_boot equal to run_list" do + expect(Chef::JSONCompat.to_json(bootstrap_context.first_boot)).to eq(Chef::JSONCompat.to_json({ run_list: run_list })) + end + end + + describe "when policy_name and policy_group are present in config" do + + let(:config) { { policy_name: "my_app_server", policy_group: "staging" } } + + it "includes them in the first_boot data and excludes run_list" do + expect(Chef::JSONCompat.to_json(bootstrap_context.first_boot)).to eq(Chef::JSONCompat.to_json({ policy_name: "my_app_server", policy_group: "staging" })) + end + + end + + describe "when an encrypted_data_bag_secret is provided" do + let(:secret) { "supersekret" } + it "reads the encrypted_data_bag_secret" do + expect(bootstrap_context.encrypted_data_bag_secret).to eq "supersekret" + end + end + + describe "to support compatibility with existing templates" do + it "sets the @config instance variable" do + expect(bootstrap_context.instance_variable_get(:@config)).to eq config + end + + it "sets the @run_list instance variable" do + expect(bootstrap_context.instance_variable_get(:@run_list)).to eq run_list + end + end + + describe "ssl_verify_mode" do + it "isn't set in the config_content by default" do + expect(bootstrap_context.config_content).not_to include("ssl_verify_mode") + end + + describe "when configured via the config hash" do + let(:config) { { node_ssl_verify_mode: "none" } } + + it "uses the config value" do + expect(bootstrap_context.config_content).to include("ssl_verify_mode :verify_none") + end + end + end + + describe "fips mode" do + before do + chef_config[:fips] = true + end + + it "sets fips mode in the client.rb" do + expect(bootstrap_context.config_content).to match(/fips true/) + end + end + + describe "verify_api_cert" do + it "isn't set in the config_content by default" do + expect(bootstrap_context.config_content).not_to include("verify_api_cert") + end + + describe "when configured via the config hash" do + let(:config) { { node_verify_api_cert: true } } + + it "uses config value" do + expect(bootstrap_context.config_content).to include("verify_api_cert true") + end + end + end + + describe "#config_log_location" do + context "when config_log_location is nil" do + let(:chef_config) { { config_log_location: nil } } + it "sets the default config_log_location in the client.rb" do + expect(bootstrap_context.get_log_location).to eq "STDOUT" + end + end + + context "when config_log_location is empty" do + let(:chef_config) { { config_log_location: "" } } + it "sets the default config_log_location in the client.rb" do + expect(bootstrap_context.get_log_location).to eq "STDOUT" + end + end + + context "when config_log_location is :win_evt" do + let(:chef_config) { { config_log_location: :win_evt } } + it "raise error when config_log_location is :win_evt " do + expect { bootstrap_context.get_log_location }.to raise_error("The value :win_evt is not supported for config_log_location on Linux Platforms \n") + end + end + + context "when config_log_location is :syslog" do + let(:chef_config) { { config_log_location: :syslog } } + it "sets the config_log_location value as :syslog in the client.rb" do + expect(bootstrap_context.get_log_location).to eq ":syslog" + end + end + + context "When config_log_location is STDOUT" do + let(:chef_config) { { config_log_location: STDOUT } } + it "Sets the config_log_location value as STDOUT in the client.rb" do + expect(bootstrap_context.get_log_location).to eq "STDOUT" + end + end + + context "when config_log_location is STDERR" do + let(:chef_config) { { config_log_location: STDERR } } + it "sets the config_log_location value as STDERR in the client.rb" do + expect(bootstrap_context.get_log_location).to eq "STDERR" + end + end + + context "when config_log_location is a path" do + let(:chef_config) { { config_log_location: "/tmp/ChefLogFile" } } + it "sets the config_log_location path in the client.rb" do + expect(bootstrap_context.get_log_location).to eq "\"/tmp/ChefLogFile\"" + end + end + + end + + describe "#version_to_install" do + context "when bootstrap_version is provided" do + let(:config) { { bootstrap_version: "awesome" } } + + it "returns bootstrap_version" do + expect(bootstrap_context.version_to_install).to eq "awesome" + end + end + + context "when bootstrap_version is not provided" do + let(:config) { { channel: "stable" } } + it "returns the currently running major version out of Chef::VERSION" do + expect(bootstrap_context.version_to_install).to eq Chef::VERSION.split(".").first + end + end + + context "and channel is other than stable" do + let(:config) { { channel: "unstable" } } + it "returns the version string 'latest'" do + expect(bootstrap_context.version_to_install).to eq "latest" + end + end + end +end diff --git a/knife/spec/unit/knife/core/cookbook_scm_repo_spec.rb b/knife/spec/unit/knife/core/cookbook_scm_repo_spec.rb new file mode 100644 index 0000000000..68a155bbbe --- /dev/null +++ b/knife/spec/unit/knife/core/cookbook_scm_repo_spec.rb @@ -0,0 +1,187 @@ +# +# Author:: Daniel DeLeo (<dan@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 "chef/knife/core/cookbook_scm_repo" + +describe Chef::Knife::CookbookSCMRepo do + before do + @repo_path = File.join(CHEF_SPEC_DATA, "cookbooks") + @stdout, @stderr, @stdin = StringIO.new, StringIO.new, StringIO.new + @ui = Chef::Knife::UI.new(@stdout, @stderr, @stdin, {}) + @cookbook_repo = Chef::Knife::CookbookSCMRepo.new(@repo_path, @ui, default_branch: "master") + + @branch_list = Mixlib::ShellOut.new + @branch_list.stdout.replace(<<-BRANCHES) + chef-vendor-apache2 + chef-vendor-build-essential + chef-vendor-dynomite + chef-vendor-ganglia + chef-vendor-graphite + chef-vendor-python + chef-vendor-absent-new + BRANCHES + end + + it "has a path to the cookbook repo" do + expect(@cookbook_repo.repo_path).to eq(@repo_path) + end + + it "has a default branch" do + expect(@cookbook_repo.default_branch).to eq("master") + end + + describe "when sanity checking the repo" do + it "exits when the directory does not exist" do + expect(::File).to receive(:directory?).with(@repo_path).and_return(false) + expect { @cookbook_repo.sanity_check }.to raise_error(SystemExit) + end + + describe "and the repo dir exists" do + before do + allow(::File).to receive(:directory?).with(@repo_path).and_return(true) + end + + it "exits when there is no git repo" do + allow(::File).to receive(:directory?).with(/.*\.git/).and_return(false) + expect { @cookbook_repo.sanity_check }.to raise_error(SystemExit) + end + + describe "and the repo is a git repo" do + before do + allow(::File).to receive(:directory?).with(File.join(@repo_path, ".git")).and_return(true) + end + + it "exits when the default branch doesn't exist" do + @nobranches = Mixlib::ShellOut.new.tap { |s| s.stdout.replace "\n" } + expect(@cookbook_repo).to receive(:shell_out!).with("git branch --no-color", cwd: @repo_path).and_return(@nobranches) + expect { @cookbook_repo.sanity_check }.to raise_error(SystemExit) + end + + describe "and the default branch exists" do + before do + @master_branch = Mixlib::ShellOut.new + @master_branch.stdout.replace "* master\n" + expect(@cookbook_repo).to receive(:shell_out!).with("git branch --no-color", cwd: @repo_path).and_return(@master_branch) + end + + it "exits when the git repo is dirty" do + @dirty_status = Mixlib::ShellOut.new + @dirty_status.stdout.replace(<<-DIRTY) + M chef/lib/chef/knife/cookbook_site_install.rb + DIRTY + expect(@cookbook_repo).to receive(:shell_out!).with("git status --porcelain", cwd: @repo_path).and_return(@dirty_status) + expect { @cookbook_repo.sanity_check }.to raise_error(SystemExit) + end + + describe "and the repo is clean" do + before do + @clean_status = Mixlib::ShellOut.new.tap { |s| s.stdout.replace("\n") } + allow(@cookbook_repo).to receive(:shell_out!).with("git status --porcelain", cwd: @repo_path).and_return(@clean_status) + end + + it "passes the sanity check" do + @cookbook_repo.sanity_check + end + + end + end + end + end + end + + it "resets to default state by checking out the default branch" do + expect(@cookbook_repo).to receive(:shell_out!).with("git checkout master", cwd: @repo_path) + @cookbook_repo.reset_to_default_state + end + + it "determines if a the pristine copy branch exists" do + expect(@cookbook_repo).to receive(:shell_out!).with("git branch --no-color", cwd: @repo_path).and_return(@branch_list) + expect(@cookbook_repo.branch_exists?("chef-vendor-apache2")).to be_truthy + expect(@cookbook_repo).to receive(:shell_out!).with("git branch --no-color", cwd: @repo_path).and_return(@branch_list) + expect(@cookbook_repo.branch_exists?("chef-vendor-nginx")).to be_falsey + end + + it "determines if a the branch not exists correctly without substring search" do + expect(@cookbook_repo).to receive(:shell_out!).twice.with("git branch --no-color", cwd: @repo_path).and_return(@branch_list) + expect(@cookbook_repo).not_to be_branch_exists("chef-vendor-absent") + expect(@cookbook_repo).to be_branch_exists("chef-vendor-absent-new") + end + + describe "when the pristine copy branch does not exist" do + it "prepares for import by creating the pristine copy branch" do + expect(@cookbook_repo).to receive(:shell_out!).with("git branch --no-color", cwd: @repo_path).and_return(@branch_list) + expect(@cookbook_repo).to receive(:shell_out!).with("git checkout -b chef-vendor-nginx", cwd: @repo_path) + @cookbook_repo.prepare_to_import("nginx") + end + end + + describe "when the pristine copy branch does exist" do + it "prepares for import by checking out the pristine copy branch" do + expect(@cookbook_repo).to receive(:shell_out!).with("git branch --no-color", cwd: @repo_path).and_return(@branch_list) + expect(@cookbook_repo).to receive(:shell_out!).with("git checkout chef-vendor-apache2", cwd: @repo_path) + @cookbook_repo.prepare_to_import("apache2") + end + end + + describe "when the pristine copy branch was not updated by the changes" do + before do + @updates = Mixlib::ShellOut.new + @updates.stdout.replace("\n") + allow(@cookbook_repo).to receive(:shell_out!).with("git status --porcelain -- apache2", cwd: @repo_path).and_return(@updates) + end + + it "shows no changes in the pristine copy" do + expect(@cookbook_repo.updated?("apache2")).to be_falsey + end + + it "does nothing to finalize the updates" do + expect(@cookbook_repo.finalize_updates_to("apache2", "1.2.3")).to be_falsey + end + end + + describe "when the pristine copy branch was updated by the changes" do + before do + @updates = Mixlib::ShellOut.new + @updates.stdout.replace(" M cookbooks/apache2/recipes/default.rb\n") + allow(@cookbook_repo).to receive(:shell_out!).with("git status --porcelain -- apache2", cwd: @repo_path).and_return(@updates) + end + + it "shows changes in the pristine copy" do + expect(@cookbook_repo.updated?("apache2")).to be_truthy + end + + it "commits the changes to the repo and tags the commit" do + expect(@cookbook_repo).to receive(:shell_out!).with("git add apache2", cwd: @repo_path) + expect(@cookbook_repo).to receive(:shell_out!).with("git commit -m \"Import apache2 version 1.2.3\" -- apache2", cwd: @repo_path) + expect(@cookbook_repo).to receive(:shell_out!).with("git tag -f cookbook-site-imported-apache2-1.2.3", cwd: @repo_path) + expect(@cookbook_repo.finalize_updates_to("apache2", "1.2.3")).to be_truthy + end + end + + describe "when a custom default branch is specified" do + before do + @cookbook_repo = Chef::Knife::CookbookSCMRepo.new(@repo_path, @ui, default_branch: "develop") + end + + it "resets to default state by checking out the default branch" do + expect(@cookbook_repo).to receive(:shell_out!).with("git checkout develop", cwd: @repo_path) + @cookbook_repo.reset_to_default_state + end + end +end diff --git a/knife/spec/unit/knife/core/cookbook_site_streaming_uploader_spec.rb b/knife/spec/unit/knife/core/cookbook_site_streaming_uploader_spec.rb new file mode 100644 index 0000000000..f40626990a --- /dev/null +++ b/knife/spec/unit/knife/core/cookbook_site_streaming_uploader_spec.rb @@ -0,0 +1,198 @@ +# +# Author:: Xabier de Zuazo (xabier@onddo.com) +# Copyright:: Copyright 2013-2016, Onddo Labs, SL. +# 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 "chef/knife/core/cookbook_site_streaming_uploader" + +class FakeTempfile + def initialize(basename) + @basename = basename + end + + def close; end + + def path + "#{@basename}.ZZZ" + end + +end + +describe Chef::Knife::Core::CookbookSiteStreamingUploader do + + let(:subject) { Chef::Knife::Core::CookbookSiteStreamingUploader } + describe "create_build_dir" do + before(:each) do + @cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks")) + @loader = Chef::CookbookLoader.new(@cookbook_repo) + @loader.load_cookbooks + allow(File).to receive(:unlink) + end + + it "should create the cookbook tmp dir" do + cookbook = @loader[:openldap] + files_count = Dir.glob(File.join(@cookbook_repo, cookbook.name.to_s, "**", "*"), File::FNM_DOTMATCH).count { |file| File.file?(file) } + + expect(Tempfile).to receive(:new).with("chef-#{cookbook.name}-build").and_return(FakeTempfile.new("chef-#{cookbook.name}-build")) + expect(FileUtils).to receive(:mkdir_p).exactly(files_count + 1).times + expect(FileUtils).to receive(:cp).exactly(files_count).times + subject.create_build_dir(cookbook) + end + + end # create_build_dir + + describe "make_request" do + + before(:each) do + @uri = "http://cookbooks.dummy.com/api/v1/cookbooks" + @secret_filename = File.join(CHEF_SPEC_DATA, "ssl/private_key.pem") + @rsa_key = File.read(@secret_filename) + response = Net::HTTPResponse.new("1.0", "200", "OK") + allow_any_instance_of(Net::HTTP).to receive(:request).and_return(response) + end + + it "should send an http request" do + expect_any_instance_of(Net::HTTP).to receive(:request) + subject.make_request(:post, @uri, "bill", @secret_filename) + end + + it "should read the private key file" do + expect(File).to receive(:read).with(@secret_filename).and_return(@rsa_key) + subject.make_request(:post, @uri, "bill", @secret_filename) + end + + it "should add the authentication signed header" do + expect_any_instance_of(Mixlib::Authentication::SigningObject).to receive(:sign).and_return({}) + subject.make_request(:post, @uri, "bill", @secret_filename) + end + + it "should be able to send post requests" do + post = Net::HTTP::Post.new(@uri, {}) + + expect(Net::HTTP::Post).to receive(:new).once.and_return(post) + expect(Net::HTTP::Put).not_to receive(:new) + expect(Net::HTTP::Get).not_to receive(:new) + subject.make_request(:post, @uri, "bill", @secret_filename) + end + + it "should be able to send put requests" do + put = Net::HTTP::Put.new(@uri, {}) + + expect(Net::HTTP::Post).not_to receive(:new) + expect(Net::HTTP::Put).to receive(:new).once.and_return(put) + expect(Net::HTTP::Get).not_to receive(:new) + subject.make_request(:put, @uri, "bill", @secret_filename) + end + + it "should be able to receive files to attach as argument" do + subject.make_request(:put, @uri, "bill", @secret_filename, { + myfile: File.new(File.join(CHEF_SPEC_DATA, "config.rb")), # a dummy file + }) + end + + it "should be able to receive strings to attach as argument" do + subject.make_request(:put, @uri, "bill", @secret_filename, { + mystring: "Lorem ipsum", + }) + end + + it "should be able to receive strings and files as argument at the same time" do + subject.make_request(:put, @uri, "bill", @secret_filename, { + myfile1: File.new(File.join(CHEF_SPEC_DATA, "config.rb")), + mystring1: "Lorem ipsum", + myfile2: File.new(File.join(CHEF_SPEC_DATA, "config.rb")), + mystring2: "Dummy text", + }) + end + + end # make_request + + describe "StreamPart" do + before(:each) do + @file = File.new(File.join(CHEF_SPEC_DATA, "config.rb")) + @stream_part = Chef::Knife::Core::CookbookSiteStreamingUploader::StreamPart.new(@file, File.size(@file)) + end + + it "should create a StreamPart" do + expect(@stream_part).to be_instance_of(Chef::Knife::Core::CookbookSiteStreamingUploader::StreamPart) + end + + it "should expose its size" do + expect(@stream_part.size).to eql(File.size(@file)) + end + + it "should read with offset and how_much" do + content = @file.read(4) + @file.rewind + expect(@stream_part.read(0, 4)).to eql(content) + end + + end # StreamPart + + describe "StringPart" do + before(:each) do + @str = "What a boring string" + @string_part = Chef::Knife::Core::CookbookSiteStreamingUploader::StringPart.new(@str) + end + + it "should create a StringPart" do + expect(@string_part).to be_instance_of(Chef::Knife::Core::CookbookSiteStreamingUploader::StringPart) + end + + it "should expose its size" do + expect(@string_part.size).to eql(@str.size) + end + + it "should read with offset and how_much" do + expect(@string_part.read(2, 4)).to eql(@str[2, 4]) + end + + end # StringPart + + describe "MultipartStream" do + before(:each) do + @string1 = "stream1" + @string2 = "stream2" + @stream1 = Chef::Knife::Core::CookbookSiteStreamingUploader::StringPart.new(@string1) + @stream2 = Chef::Knife::Core::CookbookSiteStreamingUploader::StringPart.new(@string2) + @parts = [ @stream1, @stream2 ] + + @multipart_stream = Chef::Knife::Core::CookbookSiteStreamingUploader::MultipartStream.new(@parts) + end + + it "should create a MultipartStream" do + expect(@multipart_stream).to be_instance_of(Chef::Knife::Core::CookbookSiteStreamingUploader::MultipartStream) + end + + it "should expose its size" do + expect(@multipart_stream.size).to eql(@stream1.size + @stream2.size) + end + + it "should read with how_much" do + expect(@multipart_stream.read(10)).to eql("#{@string1}#{@string2}"[0, 10]) + end + + it "should read receiving destination buffer as second argument (CHEF-4456: Ruby 2 compat)" do + dst_buf = "" + @multipart_stream.read(10, dst_buf) + expect(dst_buf).to eql("#{@string1}#{@string2}"[0, 10]) + end + + end # MultipartStream + +end diff --git a/knife/spec/unit/knife/core/gem_glob_loader_spec.rb b/knife/spec/unit/knife/core/gem_glob_loader_spec.rb new file mode 100644 index 0000000000..072dac3986 --- /dev/null +++ b/knife/spec/unit/knife/core/gem_glob_loader_spec.rb @@ -0,0 +1,242 @@ +# +# 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" + +describe Chef::Knife::SubcommandLoader::GemGlobLoader do + let(:loader) { Chef::Knife::SubcommandLoader::GemGlobLoader.new(File.join(CHEF_SPEC_DATA, "knife-site-subcommands")) } + let(:home) { File.join(CHEF_SPEC_DATA, "knife-home") } + let(:plugin_dir) { File.join(home, ".chef", "plugins", "knife") } + + before do + allow(ChefUtils).to receive(:windows?) { false } + ChefConfig::PathHelper.class_variable_set(:@@home_dir, home) + end + + after do + ChefConfig::PathHelper.class_variable_set(:@@home_dir, nil) + end + + it "builds a list of the core subcommand file require paths" do + expect(loader.subcommand_files).not_to be_empty + loader.subcommand_files.each do |require_path| + expect(require_path).to match(%r{chef/knife/.*|plugins/knife/.*}) + end + end + + it "finds files installed via rubygems" do + expect(loader.find_subcommands_via_rubygems).to include("chef/knife/node_create") + loader.find_subcommands_via_rubygems.each_value { |abs_path| expect(abs_path).to match(%r{chef/knife/.+}) } + end + + it "finds files from latest version of installed gems" do + gems = [ double("knife-ec2-0.5.12") ] + gem_files = [ + "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_base.rb", + "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_otherstuff.rb", + ] + expect($LOAD_PATH).to receive(:map).and_return([]) + if Gem::Specification.respond_to? :latest_specs + expect(Gem::Specification).to receive(:latest_specs).with(true).and_return(gems) + expect(gems[0]).to receive(:matches_for_glob).with(%r{chef/knife/\*\.rb\{(.*),\.rb,(.*)\}}).and_return(gem_files) + else + expect(Gem.source_index).to receive(:latest_specs).with(true).and_return(gems) + expect(gems[0]).to receive(:require_paths).twice.and_return(["lib"]) + expect(gems[0]).to receive(:full_gem_path).and_return("/usr/lib/ruby/gems/knife-ec2-0.5.12") + expect(Dir).to receive(:[]).with("/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/*.rb").and_return(gem_files) + end + expect(loader).to receive(:find_subcommands_via_dirglob).and_return({}) + expect(loader.subcommand_files.select { |file| file.include?("knife-ec2") }.sort).to eq(gem_files) + end + it "excludes knife version file if loaded from a gem" do + gems = [ double("knife-ec2-0.5.12") ] + gem_files = [ + "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_base.rb", + "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_otherstuff.rb", + "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/version.rb", + ] + expected_files = [ + "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_base.rb", + "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_otherstuff.rb", + ] + + expect($LOAD_PATH).to receive(:map).and_return([]) + if Gem::Specification.respond_to? :latest_specs + expect(Gem::Specification).to receive(:latest_specs).with(true).and_return(gems) + expect(gems[0]).to receive(:matches_for_glob).with(%r{chef/knife/\*\.rb\{(.*),\.rb,(.*)\}}).and_return(gem_files) + else + expect(Gem.source_index).to receive(:latest_specs).with(true).and_return(gems) + expect(gems[0]).to receive(:require_paths).twice.and_return(["lib"]) + expect(gems[0]).to receive(:full_gem_path).and_return("/usr/lib/ruby/gems/knife-ec2-0.5.12") + expect(Dir).to receive(:[]).with("/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/*.rb").and_return(gem_files) + end + expect(loader).to receive(:find_subcommands_via_dirglob).and_return({}) + expect(loader.subcommand_files.select { |file| file.include?("knife-ec2") }.sort).to eq(expected_files) + end + + it "finds files using a dirglob when rubygems is not available" do + expect(loader.find_subcommands_via_dirglob).to include("chef/knife/node_create") + loader.find_subcommands_via_dirglob.each_value { |abs_path| expect(abs_path).to match(%r{chef/knife/.+}) } + end + + it "excludes chef/knife/version.rb using a dirglob when rubygems is not available" do + expect(loader.find_subcommands_via_dirglob).to_not include("chef/knife/version") + loader.find_subcommands_via_dirglob.each_value { |abs_path| expect(abs_path).to match(%r{chef/knife/.+}) } + end + + it "finds user-specific subcommands in the user's ~/.chef directory" do + expected_command = File.join(home, ".chef", "plugins", "knife", "example_home_subcommand.rb") + expect(loader.site_subcommands).to include(expected_command) + end + + it "finds repo specific subcommands by searching for a .chef directory" do + expected_command = File.join(CHEF_SPEC_DATA, "knife-site-subcommands", "plugins", "knife", "example_subcommand.rb") + expect(loader.site_subcommands).to include(expected_command) + end + + # https://github.com/chef/chef-dk/issues/227 + # + # `knife` in ChefDK isn't from a gem install, it's directly run from a clone + # of the source, but there can be one or more versions of chef also installed + # as a gem. If the gem install contains a command that doesn't exist in the + # source tree of the "primary" chef install, it can be loaded and cause an + # error. We also want to ensure that we only load builtin commands from the + # "primary" chef install. + # + # NOTE - we need to revisit coverage now that we're moving knife to its own gem; + # or remove this test if it's no longer a supported scenario. + context "when a different version of chef is also installed as a gem" do + + let(:all_found_commands) do + [ + "/opt/chefdk/embedded/apps/chef/lib/chef/knife/bootstrap.rb", + "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_bulk_delete.rb", + "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_create.rb", + + # We use the fake version 1.0.0 because that version doesn't exist, + # which ensures it won't ever equal "chef-#{Chef::VERSION}" + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/bootstrap.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/client_bulk_delete.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/client_create.rb", + + # Test that we don't accept a version number that is different only in + # trailing characters, e.g. we are running Chef 12.0.0 but there is a + # Chef 12.0.0.rc.0 gem also: + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.rc.0/lib/chef/knife/thing.rb", + + # Test that we ignore the platform suffix when checking for different + # gem versions. + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-x86-mingw32/lib/chef/knife/valid.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-i386-mingw64/lib/chef/knife/valid-too.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-mswin32/lib/chef/knife/also-valid.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-universal-mingw32/lib/chef/knife/universal-is-valid.rb", + # ...but don't ignore the .rc / .dev parts in the case when we have + # platform suffixes + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.rc.0-x86-mingw32/lib/chef/knife/invalid.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.dev-mswin32/lib/chef/knife/invalid-too.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.dev.0-x86-mingw64/lib/chef/knife/still-invalid.rb", + + # This command is "extra" compared to what's in the embedded/apps/chef install: + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/data_bag_secret_options.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-vault-2.2.4/lib/chef/knife/decrypt.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/knife-spork-1.4.1/lib/chef/knife/spork-bump.rb", + + # These are fake commands that have names designed to test that the + # regex is strict enough + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-foo-#{Chef::VERSION}/lib/chef/knife/chef-foo.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/foo-chef-#{Chef::VERSION}/lib/chef/knife/foo-chef.rb", + + # In a real scenario, we'd use rubygems APIs to only select the most + # recent gem, but for this test we want to check that we're doing the + # right thing both when the plugin version matches and does not match + # the current chef version. Looking at + # `SubcommandLoader::MATCHES_THIS_CHEF_GEM` and + # `SubcommandLoader::MATCHES_CHEF_GEM` should make it clear why we want + # to test these two cases. + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-bar-1.0.0/lib/chef/knife/chef-bar.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/bar-chef-1.0.0/lib/chef/knife/bar-chef.rb", + ] + end + + let(:expected_valid_commands) do + [ + "/opt/chefdk/embedded/apps/chef/lib/chef/knife/bootstrap.rb", + "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_bulk_delete.rb", + "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_create.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-x86-mingw32/lib/chef/knife/valid.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-i386-mingw64/lib/chef/knife/valid-too.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-mswin32/lib/chef/knife/also-valid.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-universal-mingw32/lib/chef/knife/universal-is-valid.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-vault-2.2.4/lib/chef/knife/decrypt.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/knife-spork-1.4.1/lib/chef/knife/spork-bump.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-foo-#{Chef::VERSION}/lib/chef/knife/chef-foo.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/foo-chef-#{Chef::VERSION}/lib/chef/knife/foo-chef.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-bar-1.0.0/lib/chef/knife/chef-bar.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/bar-chef-1.0.0/lib/chef/knife/bar-chef.rb", + ] + end + + before do + expect(loader).to receive(:find_files_latest_gems).with("chef/knife/*.rb").and_return(all_found_commands) + expect(loader).to receive(:find_subcommands_via_dirglob).and_return({}) + end + + it "ignores commands from the non-matching gem install" do + expect(loader.find_subcommands_via_rubygems.values).to eq(expected_valid_commands) + end + + end + + describe "finding 3rd party plugins" do + let(:env_home) { "/home/alice" } + let(:manifest_path) { env_home + "/.chef/plugin_manifest.json" } + + before do + env_dup = ENV.to_hash + allow(ENV).to receive(:[]) { |key| env_dup[key] } + allow(ENV).to receive(:[]).with("HOME").and_return(env_home) + end + + it "searches rubygems for plugins" do + if Gem::Specification.respond_to?(:latest_specs) + expect(Gem::Specification).to receive(:latest_specs).and_call_original + else + expect(Gem.source_index).to receive(:latest_specs).and_call_original + end + loader.subcommand_files.each do |require_path| + expect(require_path).to match(%r{chef/knife/.*|plugins/knife/.*}) + end + end + + context "and HOME environment variable is not set" do + before do + allow(ENV).to receive(:[]).with("HOME").and_return(nil) + end + + it "searches rubygems for plugins" do + if Gem::Specification.respond_to?(:latest_specs) + expect(Gem::Specification).to receive(:latest_specs).and_call_original + else + expect(Gem.source_index).to receive(:latest_specs).and_call_original + end + loader.subcommand_files.each do |require_path| + expect(require_path).to match(%r{chef/knife/.*|plugins/knife/.*}) + end + end + end + end +end diff --git a/knife/spec/unit/knife/core/hashed_command_loader_spec.rb b/knife/spec/unit/knife/core/hashed_command_loader_spec.rb new file mode 100644 index 0000000000..305c928309 --- /dev/null +++ b/knife/spec/unit/knife/core/hashed_command_loader_spec.rb @@ -0,0 +1,112 @@ +# +# 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" + +describe Chef::Knife::SubcommandLoader::HashedCommandLoader do + before do + allow(ChefUtils).to receive(:windows?) { false } + end + + let(:plugin_manifest) do + { + "_autogenerated_command_paths" => { + "plugins_paths" => { + "cool_a" => ["/file/for/plugin/a"], + "cooler_b" => ["/file/for/plugin/b"], + }, + "plugins_by_category" => { + "cool" => [ + "cool_a", + ], + "cooler" => [ + "cooler_b", + ], + }, + }, + } + end + + let(:loader) do + Chef::Knife::SubcommandLoader::HashedCommandLoader.new( + File.join(CHEF_SPEC_DATA, "knife-site-subcommands"), + plugin_manifest + ) + end + + describe "#list_commands" do + before do + allow(File).to receive(:exist?).and_return(true) + end + + it "lists all commands by category when no argument is given" do + expect(loader.list_commands).to eq({ "cool" => ["cool_a"], "cooler" => ["cooler_b"] }) + end + + it "lists only commands in the given category when a category is given" do + expect(loader.list_commands("cool")).to eq({ "cool" => ["cool_a"] }) + end + + context "when the plugin path is invalid" do + before do + expect(File).to receive(:exist?).with("/file/for/plugin/b").and_return(false) + end + + it "lists all commands by category when no argument is given" do + expect(Chef::Log).to receive(:error).with(/There are plugin files specified in the knife cache that cannot be found/) + expect(Chef::Log).to receive(:error).with("Missing files:\n\t/file/for/plugin/b") + expect(loader.list_commands).to eq({}) + end + end + end + + describe "#subcommand_files" do + it "lists all the files" do + expect(loader.subcommand_files).to eq(["/file/for/plugin/a", "/file/for/plugin/b"]) + end + end + + describe "#load_commands" do + before do + allow(Kernel).to receive(:load).and_return(true) + end + + it "returns false for non-existant commands" do + expect(loader.load_command(["nothere"])).to eq(false) + end + + it "loads the correct file and returns true if the command exists" do + allow(File).to receive(:exist?).and_return(true) + expect(Kernel).to receive(:load).with("/file/for/plugin/a").and_return(true) + expect(loader.load_command(["cool_a"])).to eq(true) + end + end + + describe "#subcommand_for_args" do + it "returns the subcommands for an exact match" do + expect(loader.subcommand_for_args(["cooler_b"])).to eq("cooler_b") + end + + it "finds the right subcommand even when _'s are elided" do + expect(loader.subcommand_for_args(%w{cooler b})).to eq("cooler_b") + end + + it "returns nil if the the subcommand isn't in our manifest" do + expect(loader.subcommand_for_args(["cooler c"])).to eq(nil) + end + end +end diff --git a/knife/spec/unit/knife/core/node_editor_spec.rb b/knife/spec/unit/knife/core/node_editor_spec.rb new file mode 100644 index 0000000000..f4fbe76695 --- /dev/null +++ b/knife/spec/unit/knife/core/node_editor_spec.rb @@ -0,0 +1,211 @@ +# +# Author:: Jordan Running (<jr@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 "chef/knife/core/node_editor" + +describe Chef::Knife::NodeEditor do + let(:node_data) do + { "name" => "test_node", + "chef_environment" => "production", + "automatic" => { "foo" => "bar" }, + "default" => { "alpha" => { "bravo" => "charlie", "delta" => "echo" } }, + "normal" => { "alpha" => { "bravo" => "hotel" }, "tags" => [] }, + "override" => { "alpha" => { "bravo" => "foxtrot", "delta" => "golf" } }, + "policy_name" => nil, + "policy_group" => nil, + "run_list" => %w{role[comedy] role[drama] recipe[mystery]}, + } + end + + let(:node) { Chef::Node.from_hash(node_data) } + + let(:ui) { double "ui" } + let(:base_config) { { editor: "cat" } } + let(:config) { base_config.merge(all_attributes: false) } + + subject { described_class.new(node, ui, config) } + + describe "#view" do + it "returns a Hash with only the name, chef_environment, normal, " + + "policy_name, policy_group, and run_list properties" do + expected = node_data.select do |key,| + %w{ name chef_environment normal + policy_name policy_group run_list }.include?(key) + end + + expect(subject.view).to eq(expected) + end + + context "when config[:all_attributes] == true" do + let(:config) { base_config.merge(all_attributes: true) } + + it "returns a Hash with all of the node's properties" do + expect(subject.view).to eq(node_data) + end + end + end + + describe "#apply_updates" do + context "when the node name is changed" do + before(:each) do + allow(ui).to receive(:warn) + allow(ui).to receive(:confirm).and_return(true) + end + + it "emits a warning and prompts for confirmation" do + data = subject.view.merge("name" => "foo_new_name_node") + updated_node = subject.apply_updates(data) + + expect(ui).to have_received(:warn) + .with "Changing the name of a node results in a new node being " + + "created, test_node will not be modified or removed." + + expect(ui).to have_received(:confirm) + .with("Proceed with creation of new node") + + expect(updated_node).to be_a(Chef::Node) + end + end + + context "when config[:all_attributes] == false" do + let(:config) { base_config.merge(all_attributes: false) } + + let(:updated_data) do + subject.view.merge( + "normal" => { "alpha" => { "bravo" => "hotel2" }, "tags" => [ "xyz" ] }, + "policy_name" => "mypolicy", + "policy_group" => "prod", + "run_list" => %w{role[drama] recipe[mystery]} + ) + end + + it "returns a node with run_list and normal_attrs changed" do + updated_node = subject.apply_updates(updated_data) + expect(updated_node).to be_a(Chef::Node) + + # Expected to have been changed + expect(updated_node.normal_attrs).to eql(updated_data["normal"]) + expect(updated_node.policy_name).to eql(updated_data["policy_name"]) + expect(updated_node.policy_group).to eql(updated_data["policy_group"]) + expect(updated_node.chef_environment).to eql(updated_data["policy_group"]) + expect(updated_node.run_list.map(&:to_s)).to eql(updated_data["run_list"]) + + # Expected not to have changed + expect(updated_node.default_attrs).to eql(node.default_attrs) + expect(updated_node.override_attrs).to eql(node.override_attrs) + expect(updated_node.automatic_attrs).to eql(node.automatic_attrs) + end + end + + context "when config[:all_attributes] == true" do + let(:config) { base_config.merge(all_attributes: true) } + + let(:updated_data) do + subject.view.merge( + "default" => { "alpha" => { "bravo" => "charlie2", "delta" => "echo2" } }, + "normal" => { "alpha" => { "bravo" => "hotel2" }, "tags" => [ "xyz" ] }, + "override" => { "alpha" => { "bravo" => "foxtrot2", "delta" => "golf2" } }, + "policy_name" => "mypolicy", + "policy_group" => "prod", + "run_list" => %w{role[drama] recipe[mystery]} + ) + end + + it "returns a node with all editable properties changed" do + updated_node = subject.apply_updates(updated_data) + expect(updated_node).to be_a(Chef::Node) + + expect(updated_node.chef_environment).to eql(updated_data["policy_group"]) + expect(updated_node.automatic_attrs).to eql(updated_data["automatic"]) + expect(updated_node.normal_attrs).to eql(updated_data["normal"]) + expect(updated_node.default_attrs).to eql(updated_data["default"]) + expect(updated_node.override_attrs).to eql(updated_data["override"]) + expect(updated_node.policy_name).to eql(updated_data["policy_name"]) + expect(updated_node.policy_group).to eql(updated_data["policy_group"]) + expect(updated_node.run_list.map(&:to_s)).to eql(updated_data["run_list"]) + end + end + end + + describe "#updated?" do + context "before the node has been edited" do + it "returns false" do + expect(subject.updated?).to be false + end + end + + context "after the node has been edited" do + context "and changes were made" do + let(:updated_data) do + subject.view.merge( + "default" => { "alpha" => { "bravo" => "charlie2", "delta" => "echo2" } }, + "normal" => { "alpha" => { "bravo" => "hotel2" }, "tags" => [ "xyz" ] }, + "override" => { "alpha" => { "bravo" => "foxtrot2", "delta" => "golf2" } }, + "policy_name" => "mypolicy", + "policy_group" => "prod", + "run_list" => %w{role[drama] recipe[mystery]} + ) + end + + context "and changes affect only editable properties" do + before(:each) do + allow(ui).to receive(:edit_hash) + .with(subject.view) + .and_return(updated_data) + + subject.edit_node + end + + it "returns an array of the changed property names" do + expect(subject.updated?).to eql %w{ chef_environment normal policy_name policy_group run_list } + end + end + + context "and the changes include non-editable properties" do + before(:each) do + data = updated_data.merge("bad_property" => "bad_value") + + allow(ui).to receive(:edit_hash) + .with(subject.view) + .and_return(data) + + subject.edit_node + end + + it "returns an array of property names that doesn't include " + + "the non-editable properties" do + expect(subject.updated?).to eql %w{ chef_environment normal policy_name policy_group run_list } + end + end + end + + context "and changes were not made" do + before(:each) do + allow(ui).to receive(:edit_hash) + .with(subject.view) + .and_return(subject.view.dup) + + subject.edit_node + end + + it { is_expected.not_to be_updated } + end + end + end +end diff --git a/knife/spec/unit/knife/core/object_loader_spec.rb b/knife/spec/unit/knife/core/object_loader_spec.rb new file mode 100644 index 0000000000..00a9ed4553 --- /dev/null +++ b/knife/spec/unit/knife/core/object_loader_spec.rb @@ -0,0 +1,81 @@ +# +# Author:: Daniel DeLeo (<dan@chef.io>) +# Author:: Juanje Ojeda (<juanje.ojeda@gmail.com>) +# 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 "chef/knife/core/object_loader" + +describe Chef::Knife::Core::ObjectLoader do + before(:each) do + @knife = Chef::Knife.new + @stdout = StringIO.new + allow(@knife.ui).to receive(:stdout).and_return(@stdout) + Dir.chdir(File.join(CHEF_SPEC_DATA, "object_loader")) + end + + shared_examples_for "Chef object" do |chef_class| + it "should create a #{chef_class} object" do + expect(@object).to be_a_kind_of(chef_class) + end + + it "should has a attribute 'name'" do + expect(@object.name).to eql("test") + end + end + + { + "nodes" => Chef::Node, + "roles" => Chef::Role, + "environments" => Chef::Environment, + }.each do |repo_location, chef_class| + + describe "when the file is a #{chef_class}" do + before do + @loader = Chef::Knife::Core::ObjectLoader.new(chef_class, @knife.ui) + end + + describe "when the file is a Ruby" do + before do + @object = @loader.load_from(repo_location, "test.rb") + end + + it_behaves_like "Chef object", chef_class + end + + # NOTE: This is check for the bug described at CHEF-2352 + describe "when the file is a JSON" do + describe "and it has defined 'json_class'" do + before do + @object = @loader.load_from(repo_location, "test_json_class.json") + end + + it_behaves_like "Chef object", chef_class + end + + describe "and it has not defined 'json_class'" do + before do + @object = @loader.load_from(repo_location, "test.json") + end + + it_behaves_like "Chef object", chef_class + end + end + end + end + +end diff --git a/knife/spec/unit/knife/core/status_presenter_spec.rb b/knife/spec/unit/knife/core/status_presenter_spec.rb new file mode 100644 index 0000000000..a3f297045b --- /dev/null +++ b/knife/spec/unit/knife/core/status_presenter_spec.rb @@ -0,0 +1,54 @@ +# 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" + +describe Chef::Knife::Core::StatusPresenter do + describe "#summarize_json" do + let(:presenter) { Chef::Knife::Core::StatusPresenter.new(double(:ui), double(:config, :[] => "")) } + + let(:node) do + Chef::Node.new.tap do |n| + n.automatic_attrs["name"] = "my_node" + n.automatic_attrs["ipaddress"] = "127.0.0.1" + end + end + + let(:result) { JSON.parse(presenter.summarize_json([node])).first } + + it "uses the first of public_ipv4_addrs when present" do + node.automatic_attrs["cloud"] = { "public_ipv4_addrs" => ["2.2.2.2"] } + + expect(result["ip"]).to eq("2.2.2.2") + end + + it "falls back to ipaddress when public_ipv4_addrs is empty" do + node.automatic_attrs["cloud"] = { "public_ipv4_addrs" => [] } + + expect(result["ip"]).to eq("127.0.0.1") + end + + it "falls back to ipaddress when cloud attributes are empty" do + node.automatic_attrs["cloud"] = {} + + expect(result["ip"]).to eq("127.0.0.1") + end + + it "falls back to ipaddress when cloud attributes is not present" do + expect(result["ip"]).to eq("127.0.0.1") + end + end +end diff --git a/knife/spec/unit/knife/core/subcommand_loader_spec.rb b/knife/spec/unit/knife/core/subcommand_loader_spec.rb new file mode 100644 index 0000000000..ad53a422fe --- /dev/null +++ b/knife/spec/unit/knife/core/subcommand_loader_spec.rb @@ -0,0 +1,64 @@ +# +# 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" + +describe Chef::Knife::SubcommandLoader do + let(:loader) { Chef::Knife::SubcommandLoader.new(File.join(CHEF_SPEC_DATA, "knife-site-subcommands")) } + let(:home) { File.join(CHEF_SPEC_DATA, "knife-home") } + let(:plugin_dir) { File.join(home, ".chef", "plugins", "knife") } + + before do + allow(ChefUtils).to receive(:windows?) { false } + Chef::Util::PathHelper.class_variable_set(:@@home_dir, home) + end + + after do + Chef::Util::PathHelper.class_variable_set(:@@home_dir, nil) + end + + let(:config_dir) { File.join(CHEF_SPEC_DATA, "knife-site-subcommands") } + + describe "#for_config" do + context "when ~/.chef/plugin_manifest.json exists" do + before do + allow(File).to receive(:exist?).with(File.join(home, ".chef", "plugin_manifest.json")).and_return(true) + end + + it "creates a HashedCommandLoader with the manifest has _autogenerated_command_paths" do + allow(File).to receive(:read).with(File.join(home, ".chef", "plugin_manifest.json")).and_return("{ \"_autogenerated_command_paths\": {}}") + expect(Chef::Knife::SubcommandLoader.for_config(config_dir)).to be_a Chef::Knife::SubcommandLoader::HashedCommandLoader + end + end + + context "when ~/.chef/plugin_manifest.json does not exist" do + before do + allow(File).to receive(:exist?).with(File.join(home, ".chef", "plugin_manifest.json")).and_return(false) + end + + it "creates a GemGlobLoader" do + expect(Chef::Knife::SubcommandLoader.for_config(config_dir)).to be_a Chef::Knife::SubcommandLoader::GemGlobLoader + end + end + end + + describe "#gem_glob_loader" do + it "always creates a GemGlobLoader" do + expect(Chef::Knife::SubcommandLoader.gem_glob_loader(config_dir)).to be_a Chef::Knife::SubcommandLoader::GemGlobLoader + end + end +end diff --git a/knife/spec/unit/knife/core/ui_spec.rb b/knife/spec/unit/knife/core/ui_spec.rb new file mode 100644 index 0000000000..d5d09c0fdf --- /dev/null +++ b/knife/spec/unit/knife/core/ui_spec.rb @@ -0,0 +1,656 @@ +# +# Author:: Adam Jacob (<adam@chef.io>) +# Author:: Tim Hinderliter (<tim@chef.io>) +# Author:: Daniel DeLeo (<dan@chef.io>) +# 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" + +describe Chef::Knife::UI do + before do + @out, @err, @in = StringIO.new, StringIO.new, StringIO.new + @config = { + verbosity: 0, + yes: nil, + format: "summary", + field_separator: ".", + } + @ui = Chef::Knife::UI.new(@out, @err, @in, @config) + end + + class TestObject < OpenStruct + def self.from_hash(hsh) + new(hsh) + end + end + + describe "edit" do + ruby_for_json = { "foo" => "bar" } + ruby_from_json = TestObject.from_hash(ruby_for_json) + json_from_ruby = "{\n \"foo\": \"bar\"\n}" + json_from_editor = "{\n \"bar\": \"foo\"\n}" + ruby_from_editor = TestObject.from_hash({ "bar" => "foo" }) + my_editor = "veeeye" + temp_path = "/tmp/bar/baz" + + let(:subject) { @ui.edit_data(ruby_for_json, parse_output, object_class: klass) } + let(:parse_output) { false } + let(:klass) { nil } + + context "when editing is disabled" do + before do + @ui.config[:disable_editing] = true + stub_const("Tempfile", double) # Tempfiles should never be invoked + end + context "when parse_output is false" do + it "returns pretty json string" do + expect(subject).to eql(json_from_ruby) + end + end + context "when parse_output is true" do + let(:parse_output) { true } + let(:klass) { TestObject } + it "returns a ruby object" do + expect(subject).to eql(ruby_from_json) + end + context "but no object class is provided" do + let(:klass) { nil } + it "raises an error" do + expect { subject }.to raise_error ArgumentError, + /Please pass in the object class to hydrate or use #edit_hash/ + end + end + end + end + + context "when editing is enabled" do + before do + @ui.config[:disable_editing] = false + @ui.config[:editor] = my_editor + @mock = double("Tempfile") + expect(@mock).to receive(:sync=).with(true) + expect(@mock).to receive(:puts).with(json_from_ruby) + expect(@mock).to receive(:close) + expect(@mock).to receive(:path).at_least(:once).and_return(temp_path) + expect(Tempfile).to receive(:open).with([ "knife-edit-", ".json" ]).and_yield(@mock) + end + context "and the editor works" do + before do + expect(@ui).to receive(:system).with("#{my_editor} #{temp_path}").and_return(true) + expect(IO).to receive(:read).with(temp_path).and_return(json_from_editor) + end + + context "when parse_output is false" do + it "returns an edited pretty json string" do + expect(subject).to eql(json_from_editor) + end + end + context "when parse_output is true" do + let(:parse_output) { true } + let(:klass) { TestObject } + it "returns an edited ruby object" do + expect(subject).to eql(ruby_from_editor) + end + end + end + context "when running the editor fails with nil" do + before do + expect(@ui).to receive(:system).with("#{my_editor} #{temp_path}").and_return(nil) + expect(IO).not_to receive(:read) + end + it "throws an exception" do + expect { subject }.to raise_error(RuntimeError) + end + end + context "when running the editor fails with false" do + before do + expect(@ui).to receive(:system).with("#{my_editor} #{temp_path}").and_return(false) + expect(IO).not_to receive(:read) + end + it "throws an exception" do + expect { subject }.to raise_error(RuntimeError) + end + end + end + context "when editing and not stubbing Tempfile (semi-functional test)" do + before do + @ui.config[:disable_editing] = false + @ui.config[:editor] = my_editor + @tempfile = Tempfile.new([ "knife-edit-", ".json" ]) + expect(Tempfile).to receive(:open).with([ "knife-edit-", ".json" ]).and_yield(@tempfile) + end + + context "and the editor works" do + before do + expect(@ui).to receive(:system).with("#{my_editor} #{@tempfile.path}").and_return(true) + expect(IO).to receive(:read).with(@tempfile.path).and_return(json_from_editor) + end + + context "when parse_output is false" do + it "returns an edited pretty json string" do + expect(subject).to eql(json_from_editor) + end + it "the tempfile should have mode 0600", :unix_only do + # XXX: this looks odd because we're really testing Tempfile.new here + expect(File.stat(@tempfile.path).mode & 0777).to eql(0600) + expect(subject).to eql(json_from_editor) + end + end + + context "when parse_output is true" do + let(:parse_output) { true } + let(:klass) { TestObject } + it "returns an edited ruby object" do + expect(subject).to eql(ruby_from_editor) + end + it "the tempfile should have mode 0600", :unix_only do + # XXX: this looks odd because we're really testing Tempfile.new here + expect(File.stat(@tempfile.path).mode & 0777).to eql(0600) + expect(subject).to eql(ruby_from_editor) + end + end + end + end + end + + describe "format_list_for_display" do + it "should print the full hash if --with-uri is true" do + @ui.config[:with_uri] = true + expect(@ui.format_list_for_display({ marcy: :playground })).to eq({ marcy: :playground }) + end + + it "should print only the keys if --with-uri is false" do + @ui.config[:with_uri] = false + expect(@ui.format_list_for_display({ marcy: :playground })).to eq([ :marcy ]) + end + end + + shared_examples "an output mehthod handling IO exceptions" do |method| + it "should throw Errno::EIO exceptions" do + allow(@out).to receive(:puts).and_raise(Errno::EIO) + allow(@err).to receive(:puts).and_raise(Errno::EIO) + expect { @ui.send(method, "hi") }.to raise_error(Errno::EIO) + end + + it "should ignore Errno::EPIPE exceptions (CHEF-3516)" do + allow(@out).to receive(:puts).and_raise(Errno::EPIPE) + allow(@err).to receive(:puts).and_raise(Errno::EPIPE) + expect { @ui.send(method, "hi") }.to raise_error(SystemExit) + end + + it "should throw Errno::EPIPE exceptions with -VV (CHEF-3516)" do + @config[:verbosity] = 2 + allow(@out).to receive(:puts).and_raise(Errno::EPIPE) + allow(@err).to receive(:puts).and_raise(Errno::EPIPE) + expect { @ui.send(method, "hi") }.to raise_error(Errno::EPIPE) + end + end + + describe "output" do + it_behaves_like "an output mehthod handling IO exceptions", :output + + it "formats strings appropriately" do + @ui.output("hi") + expect(@out.string).to eq("hi\n") + end + + it "formats hashes appropriately" do + @ui.output({ "hi" => "a", "lo" => "b" }) + expect(@out.string).to eq <<~EOM + hi: a + lo: b + EOM + end + + it "formats empty hashes appropriately" do + @ui.output({}) + expect(@out.string).to eq("\n") + end + + it "formats arrays appropriately" do + @ui.output(%w{a b}) + expect(@out.string).to eq <<~EOM + a + b + EOM + end + + it "formats empty arrays appropriately" do + @ui.output([ ]) + expect(@out.string).to eq("\n") + end + + it "formats single-member arrays appropriately" do + @ui.output([ "a" ]) + expect(@out.string).to eq("a\n") + end + + it "formats nested single-member arrays appropriately" do + @ui.output([ [ "a" ] ]) + expect(@out.string).to eq("a\n") + end + + it "formats nested arrays appropriately" do + @ui.output([ %w{a b}, %w{c d}]) + expect(@out.string).to eq <<~EOM + a + b + + c + d + EOM + end + + it "formats nested arrays with single- and empty subarrays appropriately" do + @ui.output([ %w{a b}, [ "c" ], [], %w{d e}]) + expect(@out.string).to eq <<~EOM + a + b + + c + + + d + e + EOM + end + + it "formats arrays of hashes with extra lines in between for readability" do + @ui.output([ { "a" => "b", "c" => "d" }, { "x" => "y" }, { "m" => "n", "o" => "p" }]) + expect(@out.string).to eq <<~EOM + a: b + c: d + + x: y + + m: n + o: p + EOM + end + + it "formats hashes with empty array members appropriately" do + @ui.output({ "a" => [], "b" => "c" }) + expect(@out.string).to eq <<~EOM + a: + b: c + EOM + end + + it "formats hashes with single-member array values appropriately" do + @ui.output({ "a" => [ "foo" ], "b" => "c" }) + expect(@out.string).to eq <<~EOM + a: foo + b: c + EOM + end + + it "formats hashes with array members appropriately" do + @ui.output({ "a" => %w{foo bar}, "b" => "c" }) + expect(@out.string).to eq <<~EOM + a: + foo + bar + b: c + EOM + end + + it "formats hashes with single-member nested array values appropriately" do + @ui.output({ "a" => [ [ "foo" ] ], "b" => "c" }) + expect(@out.string).to eq <<~EOM + a: + foo + b: c + EOM + end + + it "formats hashes with nested array values appropriately" do + @ui.output({ "a" => [ %w{foo bar}, %w{baz bjork} ], "b" => "c" }) + # XXX: using a HEREDOC at this point results in a line with required spaces which auto-whitespace removal settings + # on editors will remove and will break this test. + expect(@out.string).to eq("a:\n foo\n bar\n \n baz\n bjork\nb: c\n") + end + + it "formats hashes with hash values appropriately" do + @ui.output({ "a" => { "aa" => "bb", "cc" => "dd" }, "b" => "c" }) + expect(@out.string).to eq <<~EOM + a: + aa: bb + cc: dd + b: c + EOM + end + + it "formats hashes with empty hash values appropriately" do + @ui.output({ "a" => {}, "b" => "c" }) + expect(@out.string).to eq <<~EOM + a: + b: c + EOM + end + end + + describe "warn" do + it_behaves_like "an output mehthod handling IO exceptions", :warn + end + + describe "error" do + it_behaves_like "an output mehthod handling IO exceptions", :warn + end + + describe "fatal" do + it_behaves_like "an output mehthod handling IO exceptions", :warn + end + + describe "format_for_display" do + it "should return the raw data" do + input = { gi: :go } + expect(@ui.format_for_display(input)).to eq(input) + end + + describe "with --attribute passed" do + it "should return the deeply nested attribute" do + input = { "gi" => { "go" => "ge" }, "id" => "sample-data-bag-item" } + @ui.config[:attribute] = "gi.go" + expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { "gi.go" => "ge" } }) + end + + it "should return multiple attributes" do + input = { "gi" => "go", "hi" => "ho", "id" => "sample-data-bag-item" } + @ui.config[:attribute] = %w{gi hi} + expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { "gi" => "go", "hi" => "ho" } }) + end + + it "should handle attributes named the same as methods" do + input = { "keys" => "values", "hi" => "ho", "id" => "sample-data-bag-item" } + @ui.config[:attribute] = "keys" + expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { "keys" => "values" } }) + end + + it "should handle nested attributes named the same as methods" do + input = { "keys" => { "keys" => "values" }, "hi" => "ho", "id" => "sample-data-bag-item" } + @ui.config[:attribute] = "keys.keys" + expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { "keys.keys" => "values" } }) + end + + it "should return the name attribute" do + input = Chef::Node.new + input.name("chef.localdomain") + @ui.config[:attribute] = "name" + expect(@ui.format_for_display(input)).to eq( { "chef.localdomain" => { "name" => "chef.localdomain" } }) + end + + it "should return a 'class' attribute and not the node.class" do + input = Chef::Node.new + input.default["class"] = "classy!" + @ui.config[:attribute] = "class" + expect(@ui.format_for_display(input)).to eq( { nil => { "class" => "classy!" } } ) + end + + it "should return the chef_environment attribute" do + input = Chef::Node.new + input.chef_environment = "production-partner-load-integration-preview-testing" + @ui.config[:attribute] = "chef_environment" + expect(@ui.format_for_display(input)).to eq( { nil => { "chef_environment" => "production-partner-load-integration-preview-testing" } } ) + end + + it "works with arrays" do + input = Chef::Node.new + input.default["array"] = %w{zero one two} + @ui.config[:attribute] = "array.1" + expect(@ui.format_for_display(input)).to eq( { nil => { "array.1" => "one" } } ) + end + + it "returns nil when given an attribute path that isn't a name or attribute" do + input = { "keys" => { "keys" => "values" }, "hi" => "ho", "id" => "sample-data-bag-item" } + non_existing_path = "nope.nada.nothingtoseehere" + @ui.config[:attribute] = non_existing_path + expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { non_existing_path => nil } }) + end + + describe "when --field-separator is passed" do + it "honors that separator" do + input = { "keys" => { "with spaces" => { "open" => { "doors" => { "with many.dots" => "when asked" } } } } } + @ui.config[:field_separator] = ";" + @ui.config[:attribute] = "keys;with spaces;open;doors;with many.dots" + expect(@ui.format_for_display(input)).to eq({ nil => { "keys;with spaces;open;doors;with many.dots" => "when asked" } }) + end + end + end + + describe "with --run-list passed" do + it "should return the run list" do + input = Chef::Node.new + input.name("sample-node") + input.run_list("role[monkey]", "role[churchmouse]") + @ui.config[:run_list] = true + response = @ui.format_for_display(input) + expect(response["sample-node"]["run_list"][0]).to eq("role[monkey]") + expect(response["sample-node"]["run_list"][1]).to eq("role[churchmouse]") + end + end + end + + describe "format_cookbook_list_for_display" do + before(:each) do + @item = { + "cookbook_name" => { + "url" => "http://url/cookbooks/cookbook", + "versions" => [ + { "version" => "3.0.0", "url" => "http://url/cookbooks/3.0.0" }, + { "version" => "2.0.0", "url" => "http://url/cookbooks/2.0.0" }, + { "version" => "1.0.0", "url" => "http://url/cookbooks/1.0.0" }, + ], + }, + } + end + + it "should return an array of the cookbooks with versions" do + expected_response = [ "cookbook_name 3.0.0 2.0.0 1.0.0" ] + response = @ui.format_cookbook_list_for_display(@item) + expect(response).to eq(expected_response) + end + + describe "with --with-uri" do + it "should return the URIs" do + response = { + "cookbook_name" => { + "1.0.0" => "http://url/cookbooks/1.0.0", + "2.0.0" => "http://url/cookbooks/2.0.0", + "3.0.0" => "http://url/cookbooks/3.0.0" }, + } + @ui.config[:with_uri] = true + expect(@ui.format_cookbook_list_for_display(@item)).to eq(response) + end + end + + context "when running on Windows" do + before(:each) do + stdout = double("StringIO", tty?: true) + allow(@ui).to receive(:stdout).and_return(stdout) + allow(ChefUtils).to receive(:windows?) { true } + Chef::Config.reset + end + + after(:each) do + Chef::Config.reset + end + + it "should have color set to true if knife config has color explicitly set to true" do + Chef::Config[:color] = true + @ui.config[:color] = true + expect(@ui.color?).to eql(true) + end + + it "should have color set to false if knife config has color explicitly set to false" do + Chef::Config[:color] = false + expect(@ui.color?).to eql(false) + end + + it "should not have color set to false by default" do + expect(@ui.color?).to eql(false) + end + end + end + + describe "color" do + context "when ui.color? => true" do + it "returns colored output" do + skip "doesn't work on systems that don't correctly have terminals setup for color" + expect(@ui).to receive(:color?).and_return(true) + expect(@ui.color("a_bus_is", :yellow)).to eql("\e[33ma_bus_is\e[0m") + end + end + + context "when ui.color? => false" do + it "returns plain output" do + expect(@ui).to receive(:color?).and_return(false) + expect(@ui.color("a_bus_is", :yellow)).to eql("a_bus_is") + end + end + end + + describe "confirm" do + let(:stdout) { StringIO.new } + let(:output) { stdout.string } + + let(:question) { "monkeys rule" } + let(:answer) { "y" } + + let(:default_choice) { nil } + let(:append_instructions) { true } + + def run_confirm + allow(@ui).to receive(:stdout).and_return(stdout) + allow(@ui.stdin).to receive(:readline).and_return(answer) + @ui.confirm(question, append_instructions, default_choice) + end + + def run_confirm_without_exit + allow(@ui).to receive(:stdout).and_return(stdout) + allow(@ui.stdin).to receive(:readline).and_return(answer) + @ui.confirm_without_exit(question, append_instructions, default_choice) + end + + shared_examples_for "confirm with positive answer" do + it "confirm should return true" do + expect(run_confirm).to be_truthy + end + + it "confirm_without_exit should return true" do + expect(run_confirm_without_exit).to be_truthy + end + end + + shared_examples_for "confirm with negative answer" do + it "confirm should exit 3" do + expect do + run_confirm + end.to raise_error(SystemExit) { |e| expect(e.status).to eq(3) } + end + + it "confirm_without_exit should return false" do + expect(run_confirm_without_exit).to be_falsey + end + end + + describe "with default choice set to true" do + let(:default_choice) { true } + + it "should show 'Y/n' in the instructions" do + run_confirm + expect(output).to include("Y/n") + end + + describe "with empty answer" do + let(:answer) { "" } + + it_behaves_like "confirm with positive answer" + end + + describe "with answer N " do + let(:answer) { "N" } + + it_behaves_like "confirm with negative answer" + end + end + + describe "with default choice set to false" do + let(:default_choice) { false } + + it "should show 'y/N' in the instructions" do + run_confirm + expect(output).to include("y/N") + end + + describe "with empty answer" do + let(:answer) { "" } + + it_behaves_like "confirm with negative answer" + end + + describe "with answer N " do + let(:answer) { "Y" } + + it_behaves_like "confirm with positive answer" + end + end + + %w{Y y}.each do |answer| + describe "with answer #{answer}" do + let(:answer) { answer } + + it_behaves_like "confirm with positive answer" + end + end + + %w{N n}.each do |answer| + describe "with answer #{answer}" do + let(:answer) { answer } + + it_behaves_like "confirm with negative answer" + end + end + + describe "with --y or --yes passed" do + it "should return true" do + @ui.config[:yes] = true + expect(run_confirm).to be_truthy + expect(output).to eq("") + end + end + end + + describe "when asking for free-form user input" do + it "asks a question and returns the answer provided by the user" do + out = StringIO.new + allow(@ui).to receive(:stdout).and_return(out) + allow(@ui).to receive(:stdin).and_return(StringIO.new("http://mychefserver.example.com\n")) + expect(@ui.ask_question("your chef server URL?")).to eq("http://mychefserver.example.com") + expect(out.string).to eq("your chef server URL?") + end + + it "suggests a default setting and returns the default when the user's response only contains whitespace" do + out = StringIO.new + allow(@ui).to receive(:stdout).and_return(out) + allow(@ui).to receive(:stdin).and_return(StringIO.new(" \n")) + expect(@ui.ask_question("your chef server URL? ", default: "http://localhost:4000")).to eq("http://localhost:4000") + expect(out.string).to eq("your chef server URL? [http://localhost:4000] ") + end + end + +end diff --git a/knife/spec/unit/knife/core/windows_bootstrap_context_spec.rb b/knife/spec/unit/knife/core/windows_bootstrap_context_spec.rb new file mode 100644 index 0000000000..af656facf0 --- /dev/null +++ b/knife/spec/unit/knife/core/windows_bootstrap_context_spec.rb @@ -0,0 +1,238 @@ +# +# Author:: Bryan McLellan <btm@loftninjas.org> +# 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 "chef/knife/core/windows_bootstrap_context" +describe Chef::Knife::Core::WindowsBootstrapContext do + let(:config) { {} } + let(:chef_config) { Chef::Config.save } # "dup" to a hash + let(:bootstrap_context) { Chef::Knife::Core::WindowsBootstrapContext.new(config, nil, chef_config, nil) } + + describe "fips" do + context "when fips is set" do + before do + chef_config[:fips] = true + end + + it "sets fips mode in the client.rb" do + expect(bootstrap_context.config_content).to match(/fips true/) + end + end + + context "when fips is not set" do + before do + chef_config[:fips] = false + end + + it "sets fips mode in the client.rb" do + expect(bootstrap_context.config_content).not_to match(/fips true/) + end + end + end + + describe "trusted_certs_script" do + let(:mock_cert_dir) { ::File.absolute_path(::File.join("spec", "assets", "fake_trusted_certs")) } + let(:script_output) { bootstrap_context.trusted_certs_script } + let(:crt_files) { ::Dir.glob(::File.join(mock_cert_dir, "*.crt")) } + let(:pem_files) { ::Dir.glob(::File.join(mock_cert_dir, "*.pem")) } + let(:other_files) { ::Dir.glob(::File.join(mock_cert_dir, "*")) - crt_files - pem_files } + + before do + bootstrap_context.instance_variable_set(:@chef_config, Mash.new(trusted_certs_dir: mock_cert_dir)) + end + + it "should echo every .crt file in the trusted_certs directory" do + crt_files.each do |f| + echo_file = ::File.read(f).gsub(/^/, "echo.") + expect(script_output).to include(::File.join("trusted_certs", ::File.basename(f))) + expect(script_output).to include(echo_file) + end + end + + it "should echo every .pem file in the trusted_certs directory" do + pem_files.each do |f| + echo_file = ::File.read(f).gsub(/^/, "echo.") + expect(script_output).to include(::File.join("trusted_certs", ::File.basename(f))) + expect(script_output).to include(echo_file) + end + end + + it "should not echo files which aren't .crt or .pem files" do + other_files.each do |f| + echo_file = ::File.read(f).gsub(/^/, "echo.") + expect(script_output).to_not include(::File.join("trusted_certs", ::File.basename(f))) + expect(script_output).to_not include(echo_file) + end + end + end + + describe "validation_key" do + before do + bootstrap_context.instance_variable_set(:@chef_config, Mash.new(validation_key: "C:\\chef\\key.pem")) + end + + it "should return false if validation_key does not exist" do + allow(::File).to receive(:expand_path).with("C:\\chef\\key.pem").and_call_original + allow(::File).to receive(:exist?).and_return(false) + expect(bootstrap_context.validation_key).to eq(false) + end + end + + describe "#get_log_location" do + + context "when config_log_location value is nil" do + it "sets STDOUT in client.rb as default" do + bootstrap_context.instance_variable_set(:@chef_config, Mash.new(config_log_location: nil)) + expect(bootstrap_context.get_log_location).to eq("STDOUT\n") + end + end + + context "when config_log_location value is empty" do + it "sets STDOUT in client.rb as default" do + bootstrap_context.instance_variable_set(:@chef_config, Mash.new(config_log_location: "")) + expect(bootstrap_context.get_log_location).to eq("STDOUT\n") + end + end + + context "when config_log_location value is STDOUT" do + it "sets STDOUT in client.rb" do + bootstrap_context.instance_variable_set(:@chef_config, Mash.new(config_log_location: STDOUT)) + expect(bootstrap_context.get_log_location).to eq("STDOUT\n") + end + end + + context "when config_log_location value is STDERR" do + it "sets STDERR in client.rb" do + bootstrap_context.instance_variable_set(:@chef_config, Mash.new(config_log_location: STDERR)) + expect(bootstrap_context.get_log_location).to eq("STDERR\n") + end + end + + context "when config_log_location value is path to a file" do + it "sets file path in client.rb" do + bootstrap_context.instance_variable_set(:@chef_config, Mash.new(config_log_location: "C:\\chef\\chef.log")) + expect(bootstrap_context.get_log_location).to eq("\"C:\\chef\\chef.log\"\n") + end + end + + context "when config_log_location value is :win_evt" do + it "sets :win_evt in client.rb" do + bootstrap_context.instance_variable_set(:@chef_config, Mash.new(config_log_location: :win_evt)) + expect(bootstrap_context.get_log_location).to eq(":win_evt\n") + end + end + + context "when config_log_location value is :syslog" do + it "raise error with message and exit" do + bootstrap_context.instance_variable_set(:@chef_config, Mash.new(config_log_location: :syslog)) + expect { bootstrap_context.get_log_location }.to raise_error("syslog is not supported for log_location on Windows OS\n") + end + end + + end + + describe "#config_content" do + before do + bootstrap_context.instance_variable_set( + :@chef_config, Mash.new( + config_log_level: :info, + config_log_location: STDOUT, + chef_server_url: "http://chef.example.com:4444", + validation_client_name: "chef-validator-testing", + file_cache_path: "c:/chef/cache", + file_backup_path: "c:/chef/backup", + cache_options: ({ path: "c:/chef/cache/checksums", skip_expires: true }) + ) + ) + end + + it "generates the config file data" do + expected = <<~EXPECTED + echo.chef_server_url "http://chef.example.com:4444" + echo.validation_client_name "chef-validator-testing" + echo.file_cache_path "C:\\\\chef\\\\cache" + echo.file_backup_path "C:\\\\chef\\\\backup" + echo.cache_options ^({:path =^> "C:\\\\chef\\\\cache\\\\checksums", :skip_expires =^> true}^) + echo.# Using default node name ^(fqdn^) + echo.log_level :auto + echo.log_location STDOUT + EXPECTED + expect(bootstrap_context.config_content).to eq expected + end + + describe "when chef_license is set" do + before do + bootstrap_context.instance_variable_set(:@chef_config, Mash.new(chef_license: "accept-no-persist")) + end + it "sets chef_license in the generated config file" do + expect(bootstrap_context.config_content).to include("chef_license \"accept-no-persist\"") + end + end + end + + describe "#start_chef" do + it "returns the expected string" do + expect(bootstrap_context.start_chef).to eq( + <<~EOH + SET "PATH=%SYSTEM32%;%SystemRoot%;%SYSTEM32%\\Wbem;%SYSTEM32%\\WindowsPowerShell\\v1.0\\;C:\\ruby\\bin;C:\\opscode\\chef\\bin;C:\\opscode\\chef\\embedded\\bin;%PATH%" + chef-client -c C:\\chef\\client.rb -j C:\\chef\\first-boot.json + EOH + ) + end + end + + describe "msi_url" do + context "when msi_url config option is not set" do + let(:config) { { channel: "stable" } } + before do + expect(bootstrap_context).to receive(:version_to_install).and_return("something") + end + + it "returns a chef.io msi url with minimal url parameters" do + reference_url = "https://omnitruck.chef.io/chef/download?p=windows&channel=stable&v=something" + expect(bootstrap_context.msi_url).to eq(reference_url) + end + + it "returns a chef.io msi url with provided url parameters substituted" do + reference_url = "https://omnitruck.chef.io/chef/download?p=windows&pv=machine&m=arch&DownloadContext=ctx&channel=stable&v=something" + expect(bootstrap_context.msi_url("machine", "arch", "ctx")).to eq(reference_url) + end + + context "when a channel is provided in config" do + let(:config) { { channel: "current" } } + it "returns a chef.io msi url with the requested channel" do + reference_url = "https://omnitruck.chef.io/chef/download?p=windows&channel=current&v=something" + expect(bootstrap_context.msi_url).to eq(reference_url) + end + end + end + + context "when msi_url config option is set" do + let(:custom_url) { "file://something" } + let(:config) { { msi_url: custom_url, install: true } } + + it "returns the overridden url" do + expect(bootstrap_context.msi_url).to eq(custom_url) + end + + it "doesn't introduce any unnecessary query parameters if provided by the template" do + expect(bootstrap_context.msi_url("machine", "arch", "ctx")).to eq(custom_url) + end + end + end +end diff --git a/knife/spec/unit/knife/data_bag_create_spec.rb b/knife/spec/unit/knife/data_bag_create_spec.rb new file mode 100644 index 0000000000..6700d886fc --- /dev/null +++ b/knife/spec/unit/knife/data_bag_create_spec.rb @@ -0,0 +1,175 @@ +# +# Author:: Daniel DeLeo (<dan@chef.io>) +# Author:: Seth Falcon (<seth@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 "tempfile" + +describe Chef::Knife::DataBagCreate do + let(:knife) do + k = Chef::Knife::DataBagCreate.new + allow(k).to receive(:rest).and_return(rest) + allow(k.ui).to receive(:stdout).and_return(stdout) + k + end + + let(:rest) { double("Chef::ServerAPI") } + let(:stdout) { StringIO.new } + + let(:bag_name) { "sudoing_admins" } + let(:item_name) { "ME" } + + let(:secret) { "abc123SECRET" } + + let(:raw_hash) { { "login_name" => "alphaomega", "id" => item_name } } + + let(:config) { {} } + + before do + Chef::Config[:node_name] = "webmonkey.example.com" + knife.name_args = [bag_name, item_name] + allow(knife).to receive(:config).and_return(config) + end + + context "when data_bag already exists" do + it "doesn't create a data bag" do + expect(knife).to receive(:create_object).and_yield(raw_hash) + expect(rest).to receive(:get).with("data/#{bag_name}") + expect(rest).to_not receive(:post).with("data", { "name" => bag_name }) + expect(knife.ui).to receive(:info).with("Data bag #{bag_name} already exists") + + knife.run + end + end + + context "when data_bag doesn't exist" do + before do + # Data bag doesn't exist by default so we mock the GET request to return 404 + exception = double("404 error", code: "404") + allow(rest).to receive(:get) + .with("data/#{bag_name}") + .and_raise(Net::HTTPClientException.new("404", exception)) + end + + it "tries to create a data bag with an invalid name when given one argument" do + knife.name_args = ["invalid&char"] + expect(Chef::DataBag).to receive(:validate_name!).with(knife.name_args[0]).and_raise(Chef::Exceptions::InvalidDataBagName) + expect { knife.run }.to exit_with_code(1) + end + + it "won't create a data bag with a reserved name for search" do + %w{node role client environment}.each do |name| + knife.name_args = [name] + expect(Chef::DataBag).to receive(:validate_name!).with(knife.name_args[0]).and_raise(Chef::Exceptions::InvalidDataBagName) + expect { knife.run }.to exit_with_code(1) + end + end + + context "when part of the name is a reserved name" do + before do + exception = double("404 error", code: "404") + %w{node role client environment}.each do |name| + allow(rest).to receive(:get) + .with("data/sudoing_#{name}_admins") + .and_raise(Net::HTTPClientException.new("404", exception)) + end + end + + it "will create a data bag containing a reserved word" do + %w{node role client environment}.each do |name| + knife.name_args = ["sudoing_#{name}_admins"] + expect(rest).to receive(:post).with("data", { "name" => knife.name_args[0] }) + expect(knife.ui).to receive(:info).with("Created data_bag[#{knife.name_args[0]}]") + + knife.run + end + end + end + + context "when given one argument" do + before do + knife.name_args = [bag_name] + end + + it "creates a data bag" do + expect(rest).to receive(:post).with("data", { "name" => bag_name }) + expect(knife.ui).to receive(:info).with("Created data_bag[#{bag_name}]") + + knife.run + end + end + + context "when given a data bag name partially matching a reserved name for search" do + %w{xnode rolex xenvironmentx xclientx}.each do |name| + let(:bag_name) { name } + + before do + knife.name_args = [bag_name] + end + + it "creates a data bag named '#{name}'" do + expect(rest).to receive(:post).with("data", { "name" => bag_name }) + expect(knife.ui).to receive(:info).with("Created data_bag[#{bag_name}]") + + knife.run + end + end + end + + context "no secret is specified for encryption" do + let(:item) do + item = Chef::DataBagItem.from_hash(raw_hash) + item.data_bag(bag_name) + item + end + + it "creates a data bag item" do + expect(knife).to receive(:create_object).and_yield(raw_hash) + expect(knife).to receive(:encryption_secret_provided?).and_return(false) + expect(rest).to receive(:post).with("data", { "name" => bag_name }).ordered + expect(rest).to receive(:post).with("data/#{bag_name}", item).ordered + + knife.run + end + end + + context "a secret is specified for encryption" do + let(:encoded_data) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_hash, secret) } + + let(:item) do + item = Chef::DataBagItem.from_hash(encoded_data) + item.data_bag(bag_name) + item + end + + it "creates an encrypted data bag item" do + expect(knife).to receive(:create_object).and_yield(raw_hash) + expect(knife).to receive(:encryption_secret_provided?).and_return(true) + expect(knife).to receive(:read_secret).and_return(secret) + expect(Chef::EncryptedDataBagItem) + .to receive(:encrypt_data_bag_item) + .with(raw_hash, secret) + .and_return(encoded_data) + expect(rest).to receive(:post).with("data", { "name" => bag_name }).ordered + expect(rest).to receive(:post).with("data/#{bag_name}", item).ordered + + knife.run + end + end + end +end diff --git a/knife/spec/unit/knife/data_bag_edit_spec.rb b/knife/spec/unit/knife/data_bag_edit_spec.rb new file mode 100644 index 0000000000..1be75ba014 --- /dev/null +++ b/knife/spec/unit/knife/data_bag_edit_spec.rb @@ -0,0 +1,126 @@ +# +# Author:: Seth Falcon (<seth@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 "tempfile" + +describe Chef::Knife::DataBagEdit do + before do + Chef::Config[:node_name] = "webmonkey.example.com" + knife.name_args = [bag_name, item_name] + allow(knife).to receive(:config).and_return(config) + end + + let(:knife) do + k = Chef::Knife::DataBagEdit.new + allow(k).to receive(:rest).and_return(rest) + allow(k.ui).to receive(:stdout).and_return(stdout) + k + end + + let(:raw_hash) { { "login_name" => "alphaomega", "id" => "item_name" } } + let(:db) { Chef::DataBagItem.from_hash(raw_hash) } + let(:raw_edited_hash) { { "login_name" => "rho", "id" => "item_name", "new_key" => "new_value" } } + + let(:rest) { double("Chef::ServerAPI") } + let(:stdout) { StringIO.new } + + let(:bag_name) { "sudoing_admins" } + let(:item_name) { "ME" } + + let(:secret) { "abc123SECRET" } + + let(:config) { {} } + + let(:is_encrypted?) { false } + let(:transmitted_hash) { raw_edited_hash } + let(:data_to_edit) { db.raw_data } + shared_examples_for "editing a data bag" do + it "correctly edits then uploads the data bag" do + expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(db) + expect(knife).to receive(:encrypted?).with(db.raw_data).and_return(is_encrypted?) + expect(knife).to receive(:edit_hash).with(data_to_edit).and_return(raw_edited_hash) + expect(rest).to receive(:put).with("data/#{bag_name}/#{item_name}", transmitted_hash).ordered + + knife.run + end + end + + it "requires data bag and item arguments" do + knife.name_args = [] + expect(stdout).to receive(:puts).twice.with(anything) + expect { knife.run }.to exit_with_code(1) + expect(stdout.string).to eq("") + end + + context "when no secret is provided" do + include_examples "editing a data bag" + end + + context "when config[:print_after] is set" do + let(:config) { { print_after: true } } + before do + expect(knife.ui).to receive(:output).with(raw_edited_hash) + end + + include_examples "editing a data bag" + end + + context "when a secret is provided" do + let!(:enc_raw_hash) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_hash, secret) } + let!(:enc_edited_hash) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_edited_hash, secret) } + let(:transmitted_hash) { enc_edited_hash } + + before(:each) do + expect(knife).to receive(:read_secret).at_least(1).times.and_return(secret) + expect(Chef::EncryptedDataBagItem).to receive(:encrypt_data_bag_item).with(raw_edited_hash, secret).and_return(enc_edited_hash) + end + + context "the data bag starts encrypted" do + let(:is_encrypted?) { true } + let(:db) { Chef::DataBagItem.from_hash(enc_raw_hash) } + # If the data bag is encrypted, it gets passed to `edit` as a hash. Otherwise, it gets passed as a DataBag + let(:data_to_edit) { raw_hash } + + before(:each) do + expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true) + end + + include_examples "editing a data bag" + end + + context "the data bag starts unencrypted" do + before(:each) do + expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).exactly(0).times + expect(knife).to receive(:encryption_secret_provided?).and_return(true) + end + + include_examples "editing a data bag" + end + end + + it "fails to edit an encrypted data bag if the secret is missing" do + expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(db) + expect(knife).to receive(:encrypted?).with(db.raw_data).and_return(true) + expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false) + + expect(knife.ui).to receive(:fatal).with("You cannot edit an encrypted data bag without providing the secret.") + expect { knife.run }.to exit_with_code(1) + end + +end diff --git a/knife/spec/unit/knife/data_bag_from_file_spec.rb b/knife/spec/unit/knife/data_bag_from_file_spec.rb new file mode 100644 index 0000000000..4d7c506d0d --- /dev/null +++ b/knife/spec/unit/knife/data_bag_from_file_spec.rb @@ -0,0 +1,174 @@ +# +# Author:: Seth Falcon (<seth@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 "chef/data_bag_item" +require "chef/encrypted_data_bag_item" +require "tempfile" + +Chef::Knife::DataBagFromFile.load_deps + +describe Chef::Knife::DataBagFromFile do + before :each do + allow(ChefUtils).to receive(:windows?) { false } + Chef::Config[:node_name] = "webmonkey.example.com" + FileUtils.mkdir_p([db_folder, db_folder2]) + db_file.write(Chef::JSONCompat.to_json(plain_data)) + db_file.flush + allow(knife).to receive(:config).and_return(config) + allow(Chef::Knife::Core::ObjectLoader).to receive(:new).and_return(loader) + end + + # We have to explicitly clean up Tempfile on Windows because it said so. + after :each do + db_file.close + db_file2.close + db_file3.close + FileUtils.rm_rf(db_folder) + FileUtils.rm_rf(db_folder2) + FileUtils.remove_entry_secure tmp_dir + end + + let(:knife) do + k = Chef::Knife::DataBagFromFile.new + allow(k).to receive(:rest).and_return(rest) + allow(k.ui).to receive(:stdout).and_return(stdout) + k + end + + let(:tmp_dir) { make_canonical_temp_directory } + let(:db_folder) { File.join(tmp_dir, data_bags_path, bag_name) } + let(:db_file) { Tempfile.new(["data_bag_from_file_test", ".json"], db_folder) } + let(:db_file2) { Tempfile.new(["data_bag_from_file_test2", ".json"], db_folder) } + let(:db_folder2) { File.join(tmp_dir, data_bags_path, bag_name2) } + let(:db_file3) { Tempfile.new(["data_bag_from_file_test3", ".json"], db_folder2) } + + def new_bag_expects(b = bag_name, d = plain_data) + data_bag = double + expect(data_bag).to receive(:data_bag).with(b) + expect(data_bag).to receive(:raw_data=).with(d) + expect(data_bag).to receive(:save) + expect(data_bag).to receive(:data_bag) + expect(data_bag).to receive(:id) + data_bag + end + + let(:loader) { double("Knife::Core::ObjectLoader") } + + let(:data_bags_path) { "data_bags" } + let(:plain_data) do + { + "id" => "item_name", + "greeting" => "hello", + "nested" => { "a1" => [1, 2, 3], "a2" => { "b1" => true } }, + } + end + let(:enc_data) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(plain_data, secret) } + + let(:rest) { double("Chef::ServerAPI") } + let(:stdout) { StringIO.new } + + let(:bag_name) { "sudoing_admins" } + let(:bag_name2) { "sudoing_admins2" } + let(:item_name) { "ME" } + + let(:secret) { "abc123SECRET" } + + let(:config) { {} } + + it "loads from a file and saves" do + knife.name_args = [bag_name, db_file.path] + expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data) + expect(Chef::DataBagItem).to receive(:new).and_return(new_bag_expects) + + knife.run + end + + it "loads all from multiple files and saves" do + knife.name_args = [ bag_name, db_file.path, db_file2.path ] + expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data) + expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file2.path).and_return(plain_data) + expect(Chef::DataBagItem).to receive(:new).twice.and_return(new_bag_expects, new_bag_expects) + + knife.run + end + + it "loads all from a folder and saves" do + knife.name_args = [ bag_name, db_folder ] + expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data) + expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file2.path).and_return(plain_data) + expect(Chef::DataBagItem).to receive(:new).twice.and_return(new_bag_expects, new_bag_expects) + + knife.run + end + + describe "loading all data bags" do + + it "loads all data bags when -a or --all options is provided" do + knife.name_args = [] + config[:all] = true + expect(loader).to receive(:find_all_object_dirs).with("./#{data_bags_path}").and_return([bag_name, bag_name2]) + expect(loader).to receive(:find_all_objects).with("./#{data_bags_path}/#{bag_name}").and_return([File.basename(db_file.path), File.basename(db_file2.path)]) + expect(loader).to receive(:find_all_objects).with("./#{data_bags_path}/#{bag_name2}").and_return([File.basename(db_file3.path)]) + expect(loader).to receive(:load_from).with(data_bags_path, bag_name, File.basename(db_file.path)).and_return(plain_data) + expect(loader).to receive(:load_from).with(data_bags_path, bag_name, File.basename(db_file2.path)).and_return(plain_data) + expect(loader).to receive(:load_from).with(data_bags_path, bag_name2, File.basename(db_file3.path)).and_return(plain_data) + expect(Chef::DataBagItem).to receive(:new).exactly(3).times.and_return(new_bag_expects, new_bag_expects, new_bag_expects(bag_name2)) + + knife.run + end + + it "loads all data bags items when -a or --all options is provided" do + knife.name_args = [bag_name2] + config[:all] = true + expect(loader).to receive(:find_all_objects).with("./#{data_bags_path}/#{bag_name2}").and_return([File.basename(db_file3.path)]) + expect(loader).to receive(:load_from).with(data_bags_path, bag_name2, File.basename(db_file3.path)).and_return(plain_data) + expect(Chef::DataBagItem).to receive(:new).and_return(new_bag_expects(bag_name2)) + + knife.run + end + + end + + describe "encrypted data bag items" do + before(:each) do + expect(knife).to receive(:encryption_secret_provided?).and_return(true) + expect(knife).to receive(:read_secret).and_return(secret) + expect(Chef::EncryptedDataBagItem).to receive(:encrypt_data_bag_item).with(plain_data, secret).and_return(enc_data) + end + + it "encrypts values when given --secret" do + knife.name_args = [bag_name, db_file.path] + expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data) + expect(Chef::DataBagItem).to receive(:new).and_return(new_bag_expects(bag_name, enc_data)) + + knife.run + end + + end + + describe "command line parsing" do + it "prints help if given no arguments" do + knife.name_args = [bag_name] + expect { knife.run }.to exit_with_code(1) + expect(stdout.string).to start_with("knife data bag from file BAG FILE|FOLDER [FILE|FOLDER..] (options)") + end + end + +end diff --git a/knife/spec/unit/knife/data_bag_secret_options_spec.rb b/knife/spec/unit/knife/data_bag_secret_options_spec.rb new file mode 100644 index 0000000000..9946b82110 --- /dev/null +++ b/knife/spec/unit/knife/data_bag_secret_options_spec.rb @@ -0,0 +1,173 @@ +# +# Author:: Tyler Ball (<tball@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 "chef/knife" +require "chef/config" +require "tempfile" + +class ExampleDataBagCommand < Chef::Knife + include Chef::Knife::DataBagSecretOptions +end + +describe Chef::Knife::DataBagSecretOptions do + let(:example_db) do + k = ExampleDataBagCommand.new + allow(k.ui).to receive(:stdout).and_return(stdout) + k + end + + let(:stdout) { StringIO.new } + + let(:secret) { "abc123SECRET" } + let(:secret_file) do + sfile = Tempfile.new("encrypted_data_bag_secret") + sfile.puts(secret) + sfile.flush + sfile + end + + after do + secret_file.close + secret_file.unlink + end + + describe "#validate_secrets" do + + it "throws an error when provided with both --secret and --secret-file on the CL" do + example_db.config[:cl_secret_file] = secret_file.path + example_db.config[:cl_secret] = secret + expect(example_db).to receive(:exit).with(1) + expect(example_db.ui).to receive(:fatal).with("Please specify only one of --secret, --secret-file") + + example_db.validate_secrets + end + + it "throws an error when provided with `secret` and `secret_file` in knife.rb" do + Chef::Config[:knife][:secret_file] = secret_file.path + Chef::Config[:knife][:secret] = secret + example_db.merge_configs + expect(example_db).to receive(:exit).with(1) + expect(example_db.ui).to receive(:fatal).with("Please specify only one of 'secret' or 'secret_file' in your config file") + + example_db.validate_secrets + end + + end + + describe "#read_secret" do + + it "returns the secret first" do + example_db.config[:cl_secret] = secret + Chef::Config[:knife][:secret] = secret + example_db.merge_configs + expect(example_db.read_secret).to eq(secret) + end + + it "returns the secret_file only if secret does not exist" do + example_db.config[:cl_secret_file] = secret_file.path + Chef::Config[:knife][:secret_file] = secret_file.path + example_db.merge_configs + expect(Chef::EncryptedDataBagItem).to receive(:load_secret).with(secret_file.path).and_return("secret file contents") + expect(example_db.read_secret).to eq("secret file contents") + end + + it "returns the secret from the knife.rb config" do + Chef::Config[:knife][:secret_file] = secret_file.path + Chef::Config[:knife][:secret] = secret + example_db.merge_configs + expect(example_db.read_secret).to eq(secret) + end + + it "returns the secret_file from the knife.rb config only if the secret does not exist" do + Chef::Config[:knife][:secret_file] = secret_file.path + example_db.merge_configs + expect(Chef::EncryptedDataBagItem).to receive(:load_secret).with(secret_file.path).and_return("secret file contents") + expect(example_db.read_secret).to eq("secret file contents") + end + + end + + describe "#encryption_secret_provided?" do + + it "returns true if the secret is passed on the CL" do + example_db.config[:cl_secret] = secret + expect(example_db.encryption_secret_provided?).to eq(true) + end + + it "returns true if the secret_file is passed on the CL" do + example_db.config[:cl_secret_file] = secret_file.path + expect(example_db.encryption_secret_provided?).to eq(true) + end + + it "returns true if --encrypt is passed on the CL and :secret is in config" do + example_db.config[:encrypt] = true + Chef::Config[:knife][:secret] = secret + example_db.merge_configs + expect(example_db.encryption_secret_provided?).to eq(true) + end + + it "returns true if --encrypt is passed on the CL and :secret_file is in config" do + example_db.config[:encrypt] = true + Chef::Config[:knife][:secret_file] = secret_file.path + example_db.merge_configs + expect(example_db.encryption_secret_provided?).to eq(true) + end + + it "throws an error if --encrypt is passed and there is not :secret or :secret_file in the config" do + example_db.config[:encrypt] = true + expect(example_db).to receive(:exit).with(1) + expect(example_db.ui).to receive(:fatal).with("No secret or secret_file specified in config, unable to encrypt item.") + example_db.encryption_secret_provided? + end + + it "returns false if no secret is passed" do + expect(example_db.encryption_secret_provided?).to eq(false) + end + + it "returns false if --encrypt is not provided and :secret is in the config" do + Chef::Config[:knife][:secret] = secret + example_db.merge_configs + expect(example_db.encryption_secret_provided?).to eq(false) + end + + it "returns false if --encrypt is not provided and :secret_file is in the config" do + Chef::Config[:knife][:secret_file] = secret_file.path + example_db.merge_configs + expect(example_db.encryption_secret_provided?).to eq(false) + end + + it "returns true if --encrypt is not provided, :secret is in the config and need_encrypt_flag is false" do + Chef::Config[:knife][:secret] = secret + example_db.merge_configs + expect(example_db.encryption_secret_provided_ignore_encrypt_flag?).to eq(true) + end + + it "returns true if --encrypt is not provided, :secret_file is in the config and need_encrypt_flag is false" do + Chef::Config[:knife][:secret_file] = secret_file.path + example_db.merge_configs + expect(example_db.encryption_secret_provided_ignore_encrypt_flag?).to eq(true) + end + + it "returns false if --encrypt is not provided and need_encrypt_flag is false" do + expect(example_db.encryption_secret_provided_ignore_encrypt_flag?).to eq(false) + end + + end + +end diff --git a/knife/spec/unit/knife/data_bag_show_spec.rb b/knife/spec/unit/knife/data_bag_show_spec.rb new file mode 100644 index 0000000000..0cc0bdf766 --- /dev/null +++ b/knife/spec/unit/knife/data_bag_show_spec.rb @@ -0,0 +1,139 @@ +# +# Author:: Adam Jacob (<adam@chef.io>) +# Author:: Seth Falcon (<seth@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 "chef/data_bag_item" +require "chef/encrypted_data_bag_item" +require "chef/json_compat" +require "tempfile" + +describe Chef::Knife::DataBagShow do + + before do + Chef::Config[:node_name] = "webmonkey.example.com" + knife.name_args = [bag_name, item_name] + allow(knife).to receive(:config).and_return(config) + end + + let(:knife) do + k = Chef::Knife::DataBagShow.new + allow(k).to receive(:rest).and_return(rest) + allow(k.ui).to receive(:stdout).and_return(stdout) + k + end + + let(:rest) { double("Chef::ServerAPI") } + let(:stdout) { StringIO.new } + + let(:bag_name) { "sudoing_admins" } + let(:item_name) { "ME" } + + let(:data_bag_contents) do + { "id" => "id", "baz" => "http://localhost:4000/data/bag_o_data/baz", + "qux" => "http://localhost:4000/data/bag_o_data/qux" } + end + let(:enc_hash) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(data_bag_contents, secret) } + let(:data_bag) { Chef::DataBagItem.from_hash(data_bag_contents) } + let(:data_bag_with_encoded_hash) { Chef::DataBagItem.from_hash(enc_hash) } + let(:enc_data_bag) { Chef::EncryptedDataBagItem.new(enc_hash, secret) } + + let(:secret) { "abc123SECRET" } + # + # let(:raw_hash) {{ "login_name" => "alphaomega", "id" => item_name }} + # + let(:config) { { format: "json" } } + + context "Data bag to show is encrypted" do + before do + allow(knife).to receive(:encrypted?).and_return(true) + end + + it "decrypts and displays the encrypted data bag when the secret is provided" do + expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true) + expect(knife).to receive(:read_secret).and_return(secret) + expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag_with_encoded_hash) + expect(knife.ui).to receive(:info).with("Encrypted data bag detected, decrypting with provided secret.") + expect(Chef::EncryptedDataBagItem).to receive(:load).with(bag_name, item_name, secret).and_return(enc_data_bag) + + expected = %q{baz: http://localhost:4000/data/bag_o_data/baz +id: id +qux: http://localhost:4000/data/bag_o_data/qux} + knife.run + expect(stdout.string.strip).to eq(expected) + end + + it "displays the encrypted data bag when the secret is not provided" do + expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false) + expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag_with_encoded_hash) + expect(knife.ui).to receive(:warn).with("Encrypted data bag detected, but no secret provided for decoding. Displaying encrypted data.") + + knife.run + expect(stdout.string.strip).to include("baz", "qux", "cipher") + end + end + + context "Data bag to show is not encrypted" do + before do + allow(knife).to receive(:encrypted?).and_return(false) + end + + it "displays the data bag" do + expect(knife).to receive(:read_secret).exactly(0).times + expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag) + + expected = %q{baz: http://localhost:4000/data/bag_o_data/baz +id: id +qux: http://localhost:4000/data/bag_o_data/qux} + knife.run + expect(stdout.string.strip).to eq(expected) + end + + context "when a secret is given" do + it "displays the data bag" do + expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true) + expect(knife).to receive(:read_secret).and_return(secret) + expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag) + expect(knife.ui).to receive(:warn).with("Unencrypted data bag detected, ignoring any provided secret options.") + + expected = %q{baz: http://localhost:4000/data/bag_o_data/baz +id: id +qux: http://localhost:4000/data/bag_o_data/qux} + knife.run + expect(stdout.string.strip).to eq(expected) + end + end + end + + it "displays the list of items in the data bag when only one @name_arg is provided" do + knife.name_args = [bag_name] + expect(Chef::DataBag).to receive(:load).with(bag_name).and_return({}) + + knife.run + expect(stdout.string.strip).to eq("") + end + + it "raises an error when no @name_args are provided" do + knife.name_args = [] + + expect { knife.run }.to exit_with_code(1) + expect(stdout.string).to start_with("knife data bag show BAG [ITEM] (options)") + end + +end diff --git a/knife/spec/unit/knife/environment_compare_spec.rb b/knife/spec/unit/knife/environment_compare_spec.rb new file mode 100644 index 0000000000..001c725624 --- /dev/null +++ b/knife/spec/unit/knife/environment_compare_spec.rb @@ -0,0 +1,112 @@ +# +# Author:: Sander Botman (<sbotman@schubergphilis.com>) +# Copyright:: Copyright 2013-2016, Sander Botman. +# 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" + +describe Chef::Knife::EnvironmentCompare do + before(:each) do + @knife = Chef::Knife::EnvironmentCompare.new + + @environments = { + "cita" => "http://localhost:4000/environments/cita", + "citm" => "http://localhost:4000/environments/citm", + } + + allow(@knife).to receive(:environment_list).and_return(@environments) + + @constraints = { + "cita" => { "foo" => "= 1.0.1", "bar" => "= 0.0.4" }, + "citm" => { "foo" => "= 1.0.1", "bar" => "= 0.0.2" }, + } + + allow(@knife).to receive(:constraint_list).and_return(@constraints) + + @cookbooks = { "foo" => "= 1.0.1", "bar" => "= 0.0.1" } + + allow(@knife).to receive(:cookbook_list).and_return(@cookbooks) + + @rest_double = double("rest") + allow(@knife).to receive(:rest).and_return(@rest_double) + @cookbook_names = %w{apache2 mysql foo bar dummy chef_handler} + @base_url = "https://server.example.com/cookbooks" + @cookbook_data = {} + @cookbook_names.each do |item| + @cookbook_data[item] = { "url" => "#{@base_url}/#{item}", + "versions" => [{ "version" => "1.0.1", + "url" => "#{@base_url}/#{item}/1.0.1" }] } + end + + allow(@rest_double).to receive(:get).with("/cookbooks?num_versions=1").and_return(@cookbook_data) + + @stdout = StringIO.new + allow(@knife.ui).to receive(:stdout).and_return(@stdout) + end + + describe "run" do + it "should display only cookbooks with version constraints" do + @knife.config[:format] = "summary" + @knife.run + @environments.each_key do |item| + expect(@stdout.string).to(match(/#{item}/)) && expect(@stdout.string.lines.count).to(be 4) + end + end + + it "should display 4 number of lines" do + @knife.config[:format] = "summary" + @knife.run + expect(@stdout.string.lines.count).to be 4 + end + end + + describe "with -m or --mismatch" do + it "should display only cookbooks that have mismatching version constraints" do + @knife.config[:format] = "summary" + @knife.config[:mismatch] = true + @knife.run + @constraints.each_value do |ver| + expect(@stdout.string).to match(/#{ver[1]}/) + end + end + + it "should display 3 number of lines" do + @knife.config[:format] = "summary" + @knife.config[:mismatch] = true + @knife.run + expect(@stdout.string.lines.count).to be 3 + end + end + + describe "with -a or --all" do + it "should display all cookbooks" do + @knife.config[:format] = "summary" + @knife.config[:all] = true + @knife.run + @constraints.each_value do |ver| + expect(@stdout.string).to match(/#{ver[1]}/) + end + end + + it "should display 8 number of lines" do + @knife.config[:format] = "summary" + @knife.config[:all] = true + @knife.run + expect(@stdout.string.lines.count).to be 8 + end + end + +end diff --git a/knife/spec/unit/knife/environment_create_spec.rb b/knife/spec/unit/knife/environment_create_spec.rb new file mode 100644 index 0000000000..0535276e9c --- /dev/null +++ b/knife/spec/unit/knife/environment_create_spec.rb @@ -0,0 +1,91 @@ +# +# Author:: Stephen Delano (<stephen@ospcode.com>) +# 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" + +describe Chef::Knife::EnvironmentCreate do + before(:each) do + @knife = Chef::Knife::EnvironmentCreate.new + allow(@knife).to receive(:msg).and_return true + allow(@knife).to receive(:output).and_return true + allow(@knife).to receive(:show_usage).and_return true + @knife.name_args = [ "production" ] + + @environment = Chef::Environment.new + allow(@environment).to receive(:save) + + allow(Chef::Environment).to receive(:new).and_return @environment + allow(@knife).to receive(:edit_data).and_return @environment + end + + describe "run" do + it "should create a new environment" do + expect(Chef::Environment).to receive(:new) + @knife.run + end + + it "should set the environment name" do + expect(@environment).to receive(:name).with("production") + @knife.run + end + + it "should not print the environment" do + expect(@knife).not_to receive(:output) + @knife.run + end + + it "should prompt you to edit the data" do + expect(@knife).to receive(:edit_data).with(@environment, object_class: Chef::Environment) + @knife.run + end + + it "should save the environment" do + expect(@environment).to receive(:save) + @knife.run + end + + it "should show usage and exit when no environment name is provided" do + @knife.name_args = [ ] + expect(@knife.ui).to receive(:fatal) + expect(@knife).to receive(:show_usage) + expect { @knife.run }.to raise_error(SystemExit) + end + + describe "with --description" do + before(:each) do + @knife.config[:description] = "This is production" + end + + it "should set the description" do + expect(@environment).to receive(:description).with("This is production") + @knife.run + end + end + + describe "with --print-after" do + before(:each) do + @knife.config[:print_after] = true + end + + it "should pretty print the environment, formatted for display" do + expect(@knife).to receive(:output).with(@environment) + @knife.run + end + end + end +end diff --git a/knife/spec/unit/knife/environment_delete_spec.rb b/knife/spec/unit/knife/environment_delete_spec.rb new file mode 100644 index 0000000000..e088f6a791 --- /dev/null +++ b/knife/spec/unit/knife/environment_delete_spec.rb @@ -0,0 +1,71 @@ +# +# Author:: Stephen Delano (<stephen@ospcode.com>) +# 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" + +describe Chef::Knife::EnvironmentDelete do + before(:each) do + @knife = Chef::Knife::EnvironmentDelete.new + allow(@knife).to receive(:msg).and_return true + allow(@knife).to receive(:output).and_return true + allow(@knife).to receive(:show_usage).and_return true + allow(@knife).to receive(:confirm).and_return true + @knife.name_args = [ "production" ] + + @environment = Chef::Environment.new + @environment.name("production") + @environment.description("Please delete me") + allow(@environment).to receive(:destroy).and_return true + allow(Chef::Environment).to receive(:load).and_return @environment + end + + it "should confirm that you want to delete" do + expect(@knife).to receive(:confirm) + @knife.run + end + + it "should load the environment" do + expect(Chef::Environment).to receive(:load).with("production") + @knife.run + end + + it "should delete the environment" do + expect(@environment).to receive(:destroy) + @knife.run + end + + it "should not print the environment" do + expect(@knife).not_to receive(:output) + @knife.run + end + + it "should show usage and exit when no environment name is provided" do + @knife.name_args = [] + expect(@knife.ui).to receive(:fatal) + expect(@knife).to receive(:show_usage) + expect { @knife.run }.to raise_error(SystemExit) + end + + describe "with --print-after" do + it "should pretty print the environment, formatted for display" do + @knife.config[:print_after] = true + expect(@knife).to receive(:output).with(@environment) + @knife.run + end + end +end diff --git a/knife/spec/unit/knife/environment_edit_spec.rb b/knife/spec/unit/knife/environment_edit_spec.rb new file mode 100644 index 0000000000..f05de2cb2f --- /dev/null +++ b/knife/spec/unit/knife/environment_edit_spec.rb @@ -0,0 +1,79 @@ +# +# Author:: Stephen Delano (<stephen@ospcode.com>) +# 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" + +describe Chef::Knife::EnvironmentEdit do + before(:each) do + @knife = Chef::Knife::EnvironmentEdit.new + allow(@knife.ui).to receive(:msg).and_return true + allow(@knife.ui).to receive(:output).and_return true + allow(@knife.ui).to receive(:show_usage).and_return true + @knife.name_args = [ "production" ] + + @environment = Chef::Environment.new + @environment.name("production") + @environment.description("Please edit me") + allow(@environment).to receive(:save).and_return true + allow(Chef::Environment).to receive(:load).and_return @environment + allow(@knife.ui).to receive(:edit_data).and_return @environment + end + + it "should load the environment" do + expect(Chef::Environment).to receive(:load).with("production") + @knife.run + end + + it "should let you edit the environment" do + expect(@knife.ui).to receive(:edit_data).with(@environment, object_class: Chef::Environment) + @knife.run + end + + it "should save the edited environment data" do + pansy = Chef::Environment.new + + @environment.name("new_environment_name") + expect(@knife.ui).to receive(:edit_data).with(@environment, object_class: Chef::Environment).and_return(pansy) + expect(pansy).to receive(:save) + @knife.run + end + + it "should not save the unedited environment data" do + expect(@environment).not_to receive(:save) + @knife.run + end + + it "should not print the environment" do + expect(@knife).not_to receive(:output) + @knife.run + end + + it "should show usage and exit when no environment name is provided" do + @knife.name_args = [] + expect(@knife).to receive(:show_usage) + expect { @knife.run }.to raise_error(SystemExit) + end + + describe "with --print-after" do + it "should pretty print the environment, formatted for display" do + @knife.config[:print_after] = true + expect(@knife.ui).to receive(:output).with(@environment) + @knife.run + end + end +end diff --git a/knife/spec/unit/knife/environment_from_file_spec.rb b/knife/spec/unit/knife/environment_from_file_spec.rb new file mode 100644 index 0000000000..fb9329eb57 --- /dev/null +++ b/knife/spec/unit/knife/environment_from_file_spec.rb @@ -0,0 +1,90 @@ +# +# Author:: Stephen Delano (<stephen@ospcode.com>) +# Author:: Seth Falcon (<seth@ospcode.com>) +# 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" + +Chef::Knife::EnvironmentFromFile.load_deps + +describe Chef::Knife::EnvironmentFromFile do + before(:each) do + allow(ChefUtils).to receive(:windows?) { false } + @knife = Chef::Knife::EnvironmentFromFile.new + @stdout = StringIO.new + allow(@knife.ui).to receive(:stdout).and_return(@stdout) + @knife.name_args = [ "spec.rb" ] + + @environment = Chef::Environment.new + @environment.name("spec") + @environment.description("runs the unit tests") + @environment.cookbook_versions({ "apt" => "= 1.2.3" }) + allow(@environment).to receive(:save).and_return true + allow(@knife.loader).to receive(:load_from).and_return @environment + end + + describe "run" do + it "loads the environment data from a file and saves it" do + expect(@knife.loader).to receive(:load_from).with("environments", "spec.rb").and_return(@environment) + expect(@environment).to receive(:save) + @knife.run + end + + context "when handling multiple environments" do + before(:each) do + @env_apple = @environment.dup + @env_apple.name("apple") + allow(@knife.loader).to receive(:load_from).with("apple.rb").and_return @env_apple + end + + it "loads multiple environments if given" do + @knife.name_args = [ "spec.rb", "apple.rb" ] + expect(@environment).to receive(:save).twice + @knife.run + end + + it "loads all environments with -a" do + allow(File).to receive(:expand_path).with("./environments/").and_return("/tmp/environments") + allow(Dir).to receive(:glob).with("/tmp/environments/*.{json,rb}").and_return(["spec.rb", "apple.rb"]) + @knife.name_args = [] + allow(@knife).to receive(:config).and_return({ all: true }) + expect(@environment).to receive(:save).twice + @knife.run + end + end + + it "should not print the environment" do + expect(@knife).not_to receive(:output) + @knife.run + end + + it "should show usage and exit if not filename is provided" do + @knife.name_args = [] + expect(@knife.ui).to receive(:fatal) + expect(@knife).to receive(:show_usage) + expect { @knife.run }.to raise_error(SystemExit) + end + + describe "with --print-after" do + it "should pretty print the environment, formatted for display" do + @knife.config[:print_after] = true + expect(@knife).to receive(:output) + @knife.run + end + end + end +end diff --git a/knife/spec/unit/knife/environment_list_spec.rb b/knife/spec/unit/knife/environment_list_spec.rb new file mode 100644 index 0000000000..4f44a93f60 --- /dev/null +++ b/knife/spec/unit/knife/environment_list_spec.rb @@ -0,0 +1,54 @@ +# +# Author:: Stephen Delano (<stephen@ospcode.com>) +# 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" + +describe Chef::Knife::EnvironmentList do + before(:each) do + @knife = Chef::Knife::EnvironmentList.new + allow(@knife).to receive(:msg).and_return true + allow(@knife).to receive(:output).and_return true + allow(@knife).to receive(:show_usage).and_return true + + @environments = { + "production" => "http://localhost:4000/environments/production", + "development" => "http://localhost:4000/environments/development", + "testing" => "http://localhost:4000/environments/testing", + } + allow(Chef::Environment).to receive(:list).and_return @environments + end + + it "should make an api call to list the environments" do + expect(Chef::Environment).to receive(:list) + @knife.run + end + + it "should print the environment names in a sorted list" do + names = @environments.keys.sort { |a, b| a <=> b } + expect(@knife).to receive(:output).with(names) + @knife.run + end + + describe "with --with-uri" do + it "should print and unsorted list of the environments and their URIs" do + @knife.config[:with_uri] = true + expect(@knife).to receive(:output).with(@environments) + @knife.run + end + end +end diff --git a/knife/spec/unit/knife/environment_show_spec.rb b/knife/spec/unit/knife/environment_show_spec.rb new file mode 100644 index 0000000000..536afcc058 --- /dev/null +++ b/knife/spec/unit/knife/environment_show_spec.rb @@ -0,0 +1,52 @@ +# +# Author:: Stephen Delano (<stephen@ospcode.com>) +# 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" + +describe Chef::Knife::EnvironmentShow do + before(:each) do + @knife = Chef::Knife::EnvironmentShow.new + allow(@knife).to receive(:msg).and_return true + allow(@knife).to receive(:output).and_return true + allow(@knife).to receive(:show_usage).and_return true + @knife.name_args = [ "production" ] + + @environment = Chef::Environment.new + @environment.name("production") + @environment.description("Look at me!") + allow(Chef::Environment).to receive(:load).and_return @environment + end + + it "should load the environment" do + expect(Chef::Environment).to receive(:load).with("production") + @knife.run + end + + it "should pretty print the environment, formatted for display" do + expect(@knife).to receive(:format_for_display).with(@environment) + expect(@knife).to receive(:output) + @knife.run + end + + it "should show usage and exit when no environment name is provided" do + @knife.name_args = [] + expect(@knife.ui).to receive(:fatal) + expect(@knife).to receive(:show_usage) + expect { @knife.run }.to raise_error(SystemExit) + end +end diff --git a/knife/spec/unit/knife/key_create_spec.rb b/knife/spec/unit/knife/key_create_spec.rb new file mode 100644 index 0000000000..91d3fc0f69 --- /dev/null +++ b/knife/spec/unit/knife/key_create_spec.rb @@ -0,0 +1,223 @@ +# +# Author:: Tyler Cloke (<tyler@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 "chef/knife/user_key_create" +require "chef/knife/client_key_create" +require "chef/knife/key_create" +require "chef/key" + +describe "key create commands that inherit knife" do + shared_examples_for "a key create command" do + let(:stderr) { StringIO.new } + let(:params) { [] } + let(:service_object) { instance_double(Chef::Knife::KeyCreate) } + let(:command) do + c = described_class.new([]) + c.ui.config[:disable_editing] = true + allow(c.ui).to receive(:stderr).and_return(stderr) + allow(c.ui).to receive(:stdout).and_return(stderr) + allow(c).to receive(:show_usage) + c + end + + context "after apply_params! is called with valid args" do + let(:params) { ["charmander"] } + before do + command.apply_params!(params) + end + + context "when the service object is called" do + it "creates a new instance of Chef::Knife::KeyCreate with the correct args" do + expect(Chef::Knife::KeyCreate).to receive(:new) + .with("charmander", command.actor_field_name, command.ui, command.config) + .and_return(service_object) + command.service_object + end + end # when the service object is called + end # after apply_params! is called with valid args + end # a key create command + + describe Chef::Knife::UserKeyCreate do + it_should_behave_like "a key create command" + # defined in key_helper.rb + it_should_behave_like "a knife key command" do + let(:service_object) { instance_double(Chef::Knife::KeyCreate) } + let(:params) { ["charmander"] } + end + end + + describe Chef::Knife::ClientKeyCreate do + it_should_behave_like "a key create command" + # defined in key_helper.rb + it_should_behave_like "a knife key command" do + let(:service_object) { instance_double(Chef::Knife::KeyCreate) } + let(:params) { ["charmander"] } + end + end +end + +describe Chef::Knife::KeyCreate do + let(:public_key) do + "-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvPo+oNPB7uuNkws0fC02 +KxSwdyqPLu0fhI1pOweNKAZeEIiEz2PkybathHWy8snSXGNxsITkf3eyvIIKa8OZ +WrlqpI3yv/5DOP8HTMCxnFuMJQtDwMcevlqebX4bCxcByuBpNYDcAHjjfLGSfMjn +E5lZpgYWwnpic4kSjYcL9ORK9nYvlWV9P/kCYmRhIjB4AhtpWRiOfY/TKi3P2LxT +IjSmiN/ihHtlhV/VSnBJ5PzT/lRknlrJ4kACoz7Pq9jv+aAx5ft/xE9yDa2DYs0q +Tfuc9dUYsFjptWYrV6pfEQ+bgo1OGBXORBFcFL+2D7u9JYquKrMgosznHoEkQNLo +0wIDAQAB +-----END PUBLIC KEY-----" + end + let(:config) { {} } + let(:actor) { "charmander" } + let(:ui) { instance_double("Chef::Knife::UI") } + + shared_examples_for "key create run command" do + let(:key_create_object) do + described_class.new(actor, actor_field_name, ui, config) + end + + context "when public_key and key_name weren't passed" do + it "raises a Chef::Exceptions::KeyCommandInputError with the proper error message" do + expect { key_create_object.run }.to raise_error(Chef::Exceptions::KeyCommandInputError, key_create_object.public_key_or_key_name_error_msg) + end + end + + context "when the command is run" do + let(:expected_hash) do + { + actor_field_name => "charmander", + } + end + + before do + allow(File).to receive(:read).and_return(public_key) + allow(File).to receive(:expand_path) + + allow(key_create_object).to receive(:output_private_key_to_file) + allow(key_create_object).to receive(:display_private_key) + allow(key_create_object).to receive(:edit_hash).and_return(expected_hash) + allow(key_create_object).to receive(:create_key_from_hash).and_return(Chef::Key.from_hash(expected_hash)) + allow(key_create_object).to receive(:display_info) + end + + context "when a valid hash is passed" do + let(:key_name) { "charmander-key" } + let(:valid_expiration_date) { "2020-12-24T21:00:00Z" } + let(:expected_hash) do + { + actor_field_name => "charmander", + "public_key" => public_key, + "expiration_date" => valid_expiration_date, + "key_name" => key_name, + } + end + before do + key_create_object.config[:public_key] = "public_key_path" + key_create_object.config[:expiration_Date] = valid_expiration_date, + key_create_object.config[:key_name] = key_name + end + + it "creates the proper hash" do + expect(key_create_object).to receive(:create_key_from_hash).with(expected_hash) + key_create_object.run + end + end + + context "when public_key is passed" do + let(:expected_hash) do + { + actor_field_name => "charmander", + "public_key" => public_key, + } + end + before do + key_create_object.config[:public_key] = "public_key_path" + end + + it "calls File.expand_path with the public_key input" do + expect(File).to receive(:expand_path).with("public_key_path") + key_create_object.run + end + end # when public_key is passed + + context "when public_key isn't passed and key_name is" do + let(:expected_hash) do + { + actor_field_name => "charmander", + "name" => "charmander-key", + "create_key" => true, + } + end + before do + key_create_object.config[:key_name] = "charmander-key" + end + + it "should set create_key to true" do + expect(key_create_object).to receive(:create_key_from_hash).with(expected_hash) + key_create_object.run + end + end + + context "when the server returns a private key" do + let(:expected_hash) do + { + actor_field_name => "charmander", + "public_key" => public_key, + "private_key" => "super_private", + } + end + + before do + key_create_object.config[:public_key] = "public_key_path" + end + + context "when file is not passed" do + it "calls display_private_key with the private_key" do + expect(key_create_object).to receive(:display_private_key).with("super_private") + key_create_object.run + end + end + + context "when file is passed" do + before do + key_create_object.config[:file] = "/fake/file" + end + + it "calls output_private_key_to_file with the private_key" do + expect(key_create_object).to receive(:output_private_key_to_file).with("super_private") + key_create_object.run + end + end + end # when the server returns a private key + end # when the command is run + end # key create run command" + + context "when actor_field_name is 'user'" do + it_should_behave_like "key create run command" do + let(:actor_field_name) { "user" } + end + end + + context "when actor_field_name is 'client'" do + it_should_behave_like "key create run command" do + let(:actor_field_name) { "client" } + end + end +end diff --git a/knife/spec/unit/knife/key_delete_spec.rb b/knife/spec/unit/knife/key_delete_spec.rb new file mode 100644 index 0000000000..ff669446bb --- /dev/null +++ b/knife/spec/unit/knife/key_delete_spec.rb @@ -0,0 +1,133 @@ +# +# Author:: Tyler Cloke (<tyler@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 "chef/knife/user_key_delete" +require "chef/knife/client_key_delete" +require "chef/knife/key_delete" +require "chef/key" + +describe "key delete commands that inherit knife" do + shared_examples_for "a key delete command" do + let(:stderr) { StringIO.new } + let(:params) { [] } + let(:service_object) { instance_double(Chef::Knife::KeyDelete) } + let(:command) do + c = described_class.new([]) + c.ui.config[:disable_editing] = true + allow(c.ui).to receive(:stderr).and_return(stderr) + allow(c.ui).to receive(:stdout).and_return(stderr) + allow(c).to receive(:show_usage) + c + end + + context "after apply_params! is called with valid args" do + let(:params) { %w{charmander charmander-key} } + before do + command.apply_params!(params) + end + + context "when the service object is called" do + it "creates a new instance of Chef::Knife::KeyDelete with the correct args" do + expect(Chef::Knife::KeyDelete).to receive(:new) + .with("charmander-key", "charmander", command.actor_field_name, command.ui) + .and_return(service_object) + command.service_object + end + end # when the service object is called + end # after apply_params! is called with valid args + end # a key delete command + + describe Chef::Knife::UserKeyDelete do + it_should_behave_like "a key delete command" + # defined in key_helpers.rb + it_should_behave_like "a knife key command with a keyname as the second arg" + it_should_behave_like "a knife key command" do + let(:service_object) { instance_double(Chef::Knife::KeyDelete) } + let(:params) { %w{charmander charmander-key} } + end + end + + describe Chef::Knife::ClientKeyDelete do + it_should_behave_like "a key delete command" + # defined in key_helpers.rb + it_should_behave_like "a knife key command with a keyname as the second arg" + it_should_behave_like "a knife key command" do + let(:service_object) { instance_double(Chef::Knife::KeyDelete) } + let(:params) { %w{charmander charmander-key} } + end + end +end + +describe Chef::Knife::KeyDelete do + let(:actor) { "charmander" } + let(:keyname) { "charmander-key" } + let(:ui) { instance_double("Chef::Knife::UI") } + + shared_examples_for "key delete run command" do + let(:key_delete_object) do + described_class.new(keyname, actor, actor_field_name, ui) + end + + before do + allow_any_instance_of(Chef::Key).to receive(:destroy) + allow(key_delete_object).to receive(:print_destroyed) + allow(key_delete_object).to receive(:confirm!) + end + + context "when the command is run" do + it "calls Chef::Key.new with the proper input" do + expect(Chef::Key).to receive(:new).with(actor, actor_field_name).and_call_original + key_delete_object.run + end + + it "calls name on the Chef::Key instance with the proper input" do + expect_any_instance_of(Chef::Key).to receive(:name).with(keyname) + key_delete_object.run + end + + it "calls destroy on the Chef::Key instance" do + expect_any_instance_of(Chef::Key).to receive(:destroy).once + key_delete_object.run + end + + it "calls confirm!" do + expect(key_delete_object).to receive(:confirm!) + key_delete_object.run + end + + it "calls print_destroyed" do + expect(key_delete_object).to receive(:print_destroyed) + key_delete_object.run + end + end # when the command is run + + end # key delete run command + + context "when actor_field_name is 'user'" do + it_should_behave_like "key delete run command" do + let(:actor_field_name) { "user" } + end + end + + context "when actor_field_name is 'client'" do + it_should_behave_like "key delete run command" do + let(:actor_field_name) { "client" } + end + end +end diff --git a/knife/spec/unit/knife/key_edit_spec.rb b/knife/spec/unit/knife/key_edit_spec.rb new file mode 100644 index 0000000000..ae58d281b7 --- /dev/null +++ b/knife/spec/unit/knife/key_edit_spec.rb @@ -0,0 +1,264 @@ +# +# Author:: Tyler Cloke (<tyler@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 "chef/knife/user_key_edit" +require "chef/knife/client_key_edit" +require "chef/knife/key_edit" +require "chef/key" + +describe "key edit commands that inherit knife" do + shared_examples_for "a key edit command" do + let(:stderr) { StringIO.new } + let(:params) { [] } + let(:service_object) { instance_double(Chef::Knife::KeyEdit) } + let(:command) do + c = described_class.new([]) + c.ui.config[:disable_editing] = true + allow(c.ui).to receive(:stderr).and_return(stderr) + allow(c.ui).to receive(:stdout).and_return(stderr) + allow(c).to receive(:show_usage) + c + end + + context "after apply_params! is called with valid args" do + let(:params) { %w{charmander charmander-key} } + before do + command.apply_params!(params) + end + + context "when the service object is called" do + it "creates a new instance of Chef::Knife::KeyEdit with the correct args" do + expect(Chef::Knife::KeyEdit).to receive(:new) + .with("charmander-key", "charmander", command.actor_field_name, command.ui, command.config) + .and_return(service_object) + command.service_object + end + end # when the service object is called + end # after apply_params! is called with valid args + end # a key edit command + + describe Chef::Knife::UserKeyEdit do + it_should_behave_like "a key edit command" + # defined in key_helpers.rb + it_should_behave_like "a knife key command with a keyname as the second arg" + it_should_behave_like "a knife key command" do + let(:service_object) { instance_double(Chef::Knife::KeyEdit) } + let(:params) { %w{charmander charmander-key} } + end + end + + describe Chef::Knife::ClientKeyEdit do + it_should_behave_like "a key edit command" + # defined in key_helpers.rb + it_should_behave_like "a knife key command with a keyname as the second arg" + it_should_behave_like "a knife key command" do + let(:service_object) { instance_double(Chef::Knife::KeyEdit) } + let(:params) { %w{charmander charmander-key} } + end + end +end + +describe Chef::Knife::KeyEdit do + let(:public_key) do + "-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvPo+oNPB7uuNkws0fC02 +KxSwdyqPLu0fhI1pOweNKAZeEIiEz2PkybathHWy8snSXGNxsITkf3eyvIIKa8OZ +WrlqpI3yv/5DOP8HTMCxnFuMJQtDwMcevlqebX4bCxcByuBpNYDcAHjjfLGSfMjn +E5lZpgYWwnpic4kSjYcL9ORK9nYvlWV9P/kCYmRhIjB4AhtpWRiOfY/TKi3P2LxT +IjSmiN/ihHtlhV/VSnBJ5PzT/lRknlrJ4kACoz7Pq9jv+aAx5ft/xE9yDa2DYs0q +Tfuc9dUYsFjptWYrV6pfEQ+bgo1OGBXORBFcFL+2D7u9JYquKrMgosznHoEkQNLo +0wIDAQAB +-----END PUBLIC KEY-----" + end + let(:config) { {} } + let(:actor) { "charmander" } + let(:keyname) { "charmander-key" } + let(:ui) { instance_double("Chef::Knife::UI") } + + shared_examples_for "key edit run command" do + let(:key_edit_object) do + described_class.new(keyname, actor, actor_field_name, ui, config) + end + + context "when the command is run" do + let(:expected_hash) do + { + actor_field_name => "charmander", + } + end + let(:new_keyname) { "charizard-key" } + + before do + allow(File).to receive(:read).and_return(public_key) + allow(File).to receive(:expand_path) + + allow(key_edit_object).to receive(:output_private_key_to_file) + allow(key_edit_object).to receive(:display_private_key) + allow(key_edit_object).to receive(:edit_hash).and_return(expected_hash) + allow(key_edit_object).to receive(:display_info) + end + + context "when public_key and create_key are passed" do + before do + key_edit_object.config[:public_key] = "public_key_path" + key_edit_object.config[:create_key] = true + end + + it "raises a Chef::Exceptions::KeyCommandInputError with the proper error message" do + expect { key_edit_object.run }.to raise_error(Chef::Exceptions::KeyCommandInputError, key_edit_object.public_key_and_create_key_error_msg) + end + end + + context "when key_name is passed" do + let(:expected_hash) do + { + actor_field_name => "charmander", + "name" => new_keyname, + } + end + before do + key_edit_object.config[:key_name] = new_keyname + allow_any_instance_of(Chef::Key).to receive(:update) + end + + it "update_key_from_hash gets passed a hash with new key name" do + expect(key_edit_object).to receive(:update_key_from_hash).with(expected_hash).and_return(Chef::Key.from_hash(expected_hash)) + key_edit_object.run + end + + it "Chef::Key.update is passed a string containing the original keyname" do + expect_any_instance_of(Chef::Key).to receive(:update).with(/#{keyname}/).and_return(Chef::Key.from_hash(expected_hash)) + key_edit_object.run + end + + it "Chef::Key.update is not passed a string containing the new keyname" do + expect_any_instance_of(Chef::Key).not_to receive(:update).with(/#{new_keyname}/) + allow_any_instance_of(Chef::Key).to receive(:update).and_return(Chef::Key.from_hash(expected_hash)) + key_edit_object.run + end + end + + context "when public_key, key_name, and expiration_date are passed" do + let(:expected_hash) do + { + actor_field_name => "charmander", + "public_key" => public_key, + "name" => new_keyname, + "expiration_date" => "infinity", + } + end + before do + key_edit_object.config[:public_key] = "this-public-key" + key_edit_object.config[:key_name] = new_keyname + key_edit_object.config[:expiration_date] = "infinity" + allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash)) + end + + it "passes the right hash to update_key_from_hash" do + expect(key_edit_object).to receive(:update_key_from_hash).with(expected_hash) + key_edit_object.run + end + end + + context "when create_key is passed" do + let(:expected_hash) do + { + actor_field_name => "charmander", + "create_key" => true, + } + end + + before do + key_edit_object.config[:create_key] = true + allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash)) + end + + it "passes the right hash to update_key_from_hash" do + expect(key_edit_object).to receive(:update_key_from_hash).with(expected_hash) + key_edit_object.run + end + end + + context "when public_key is passed" do + let(:expected_hash) do + { + actor_field_name => "charmander", + "public_key" => public_key, + } + end + before do + allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash)) + key_edit_object.config[:public_key] = "public_key_path" + end + + it "calls File.expand_path with the public_key input" do + expect(File).to receive(:expand_path).with("public_key_path") + key_edit_object.run + end + end # when public_key is passed + + context "when the server returns a private key" do + let(:expected_hash) do + { + actor_field_name => "charmander", + "public_key" => public_key, + "private_key" => "super_private", + } + end + + before do + allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash)) + key_edit_object.config[:public_key] = "public_key_path" + end + + context "when file is not passed" do + it "calls display_private_key with the private_key" do + expect(key_edit_object).to receive(:display_private_key).with("super_private") + key_edit_object.run + end + end + + context "when file is passed" do + before do + key_edit_object.config[:file] = "/fake/file" + end + + it "calls output_private_key_to_file with the private_key" do + expect(key_edit_object).to receive(:output_private_key_to_file).with("super_private") + key_edit_object.run + end + end + end # when the server returns a private key + + end # when the command is run + + end # key edit run command + + context "when actor_field_name is 'user'" do + it_should_behave_like "key edit run command" do + let(:actor_field_name) { "user" } + end + end + + context "when actor_field_name is 'client'" do + it_should_behave_like "key edit run command" do + let(:actor_field_name) { "client" } + end + end +end diff --git a/knife/spec/unit/knife/key_helper.rb b/knife/spec/unit/knife/key_helper.rb new file mode 100644 index 0000000000..c58d383703 --- /dev/null +++ b/knife/spec/unit/knife/key_helper.rb @@ -0,0 +1,74 @@ +# +# Author:: Tyler Cloke (<tyler@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" + +shared_examples_for "a knife key command" do + let(:stderr) { StringIO.new } + let(:params) { [] } + let(:command) do + c = described_class.new([]) + c.ui.config[:disable_editing] = true + allow(c.ui).to receive(:stderr).and_return(stderr) + allow(c.ui).to receive(:stdout).and_return(stderr) + allow(c).to receive(:show_usage) + c + end + + context "before apply_params! is called" do + context "when apply_params! is called with invalid args" do + it "shows the usage" do + expect(command).to receive(:show_usage) + expect { command.apply_params!(params) }.to exit_with_code(1) + end + + it "outputs the proper error" do + expect { command.apply_params!(params) }.to exit_with_code(1) + expect(stderr.string).to include(command.actor_missing_error) + end + + it "exits 1" do + expect { command.apply_params!(params) }.to exit_with_code(1) + end + end + end # before apply_params! is called + + context "after apply_params! is called with valid args" do + let(:params) { ["charmander"] } + before do + command.apply_params!(params) + end + + it "properly defines the actor" do + expect(command.actor).to eq("charmander") + end + end # after apply_params! is called with valid args + + context "when the command is run" do + before do + allow(command).to receive(:service_object).and_return(service_object) + allow(command).to receive(:name_args).and_return(["charmander"]) + end + + context "when the command is successful" do + before do + expect(service_object).to receive(:run) + end + end + end +end # a knife key command diff --git a/knife/spec/unit/knife/key_list_spec.rb b/knife/spec/unit/knife/key_list_spec.rb new file mode 100644 index 0000000000..3cb8a1c58d --- /dev/null +++ b/knife/spec/unit/knife/key_list_spec.rb @@ -0,0 +1,216 @@ +# +# Author:: Tyler Cloke (<tyler@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 "chef/knife/user_key_list" +require "chef/knife/client_key_list" +require "chef/knife/key_list" +require "chef/key" + +describe "key list commands that inherit knife" do + shared_examples_for "a key list command" do + let(:stderr) { StringIO.new } + let(:params) { [] } + let(:service_object) { instance_double(Chef::Knife::KeyList) } + let(:command) do + c = described_class.new([]) + c.ui.config[:disable_editing] = true + allow(c.ui).to receive(:stderr).and_return(stderr) + allow(c.ui).to receive(:stdout).and_return(stderr) + allow(c).to receive(:show_usage) + c + end + + context "after apply_params! is called with valid args" do + let(:params) { ["charmander"] } + before do + command.apply_params!(params) + end + + context "when the service object is called" do + it "creates a new instance of Chef::Knife::KeyList with the correct args" do + expect(Chef::Knife::KeyList).to receive(:new) + .with("charmander", command.list_method, command.ui, command.config) + .and_return(service_object) + command.service_object + end + end # when the service object is called + end # after apply_params! is called with valid args + end # a key list command + + describe Chef::Knife::UserKeyList do + it_should_behave_like "a key list command" + # defined in key_helpers.rb + it_should_behave_like "a knife key command" do + let(:service_object) { instance_double(Chef::Knife::KeyList) } + let(:params) { ["charmander"] } + end + end + + describe Chef::Knife::ClientKeyList do + it_should_behave_like "a key list command" + # defined in key_helpers.rb + it_should_behave_like "a knife key command" do + let(:service_object) { instance_double(Chef::Knife::KeyList) } + let(:params) { ["charmander"] } + end + end +end + +describe Chef::Knife::KeyList do + let(:config) { {} } + let(:actor) { "charmander" } + let(:ui) { instance_double("Chef::Knife::UI") } + + shared_examples_for "key list run command" do + let(:key_list_object) do + described_class.new(actor, list_method, ui, config) + end + + before do + allow(Chef::Key).to receive(list_method).and_return(http_response) + allow(key_list_object).to receive(:display_info) + # simply pass the string though that colorize takes in + allow(key_list_object).to receive(:colorize).with(kind_of(String)) do |input| + input + end + end + + context "when only_expired and only_non_expired were both passed" do + before do + key_list_object.config[:only_expired] = true + key_list_object.config[:only_non_expired] = true + end + + it "raises a Chef::Exceptions::KeyCommandInputError with the proper error message" do + expect { key_list_object.run }.to raise_error(Chef::Exceptions::KeyCommandInputError, key_list_object.expired_and_non_expired_msg) + end + end + + context "when the command is run" do + before do + key_list_object.config[:only_expired] = false + key_list_object.config[:only_non_expired] = false + key_list_object.config[:with_details] = false + end + + it "calls Chef::Key with the proper list command and input" do + expect(Chef::Key).to receive(list_method).with(actor) + key_list_object.run + end + + it "displays all the keys" do + expect(key_list_object).to receive(:display_info).with(/non-expired/).twice + expect(key_list_object).to receive(:display_info).with(/out-of-date/).once + key_list_object.run + end + + context "when only_expired is called" do + before do + key_list_object.config[:only_expired] = true + end + + it "excludes displaying non-expired keys" do + expect(key_list_object).to receive(:display_info).with(/non-expired/).exactly(0).times + key_list_object.run + end + + it "displays the expired keys" do + expect(key_list_object).to receive(:display_info).with(/out-of-date/).once + key_list_object.run + end + end # when only_expired is called + + context "when only_non_expired is called" do + before do + key_list_object.config[:only_non_expired] = true + end + + it "excludes displaying expired keys" do + expect(key_list_object).to receive(:display_info).with(/out-of-date/).exactly(0).times + key_list_object.run + end + + it "displays the non-expired keys" do + expect(key_list_object).to receive(:display_info).with(/non-expired/).twice + key_list_object.run + end + end # when only_expired is called + + context "when with_details is false" do + before do + key_list_object.config[:with_details] = false + end + + it "does not display the uri" do + expect(key_list_object).to receive(:display_info).with(/https/).exactly(0).times + key_list_object.run + end + + it "does not display the expired status" do + expect(key_list_object).to receive(:display_info).with(/\(expired\)/).exactly(0).times + key_list_object.run + end + end # when with_details is false + + context "when with_details is true" do + before do + key_list_object.config[:with_details] = true + end + + it "displays the uri" do + expect(key_list_object).to receive(:display_info).with(/https/).exactly(3).times + key_list_object.run + end + + it "displays the expired status" do + expect(key_list_object).to receive(:display_info).with(/\(expired\)/).once + key_list_object.run + end + end # when with_details is true + + end # when the command is run + + end # key list run command + + context "when list_method is :list_by_user" do + it_should_behave_like "key list run command" do + let(:list_method) { :list_by_user } + let(:http_response) do + [ + { "uri" => "https://api.opscode.piab/users/charmander/keys/non-expired1", "name" => "non-expired1", "expired" => false }, + { "uri" => "https://api.opscode.piab/users/charmander/keys/non-expired2", "name" => "non-expired2", "expired" => false }, + { "uri" => "https://api.opscode.piab/users/mary/keys/out-of-date", "name" => "out-of-date", "expired" => true }, + ] + end + end + end + + context "when list_method is :list_by_client" do + it_should_behave_like "key list run command" do + let(:list_method) { :list_by_client } + let(:http_response) do + [ + { "uri" => "https://api.opscode.piab/organizations/pokemon/clients/charmander/keys/non-expired1", "name" => "non-expired1", "expired" => false }, + { "uri" => "https://api.opscode.piab/organizations/pokemon/clients/charmander/keys/non-expired2", "name" => "non-expired2", "expired" => false }, + { "uri" => "https://api.opscode.piab/organizations/pokemon/clients/mary/keys/out-of-date", "name" => "out-of-date", "expired" => true }, + ] + end + end + end +end diff --git a/knife/spec/unit/knife/key_show_spec.rb b/knife/spec/unit/knife/key_show_spec.rb new file mode 100644 index 0000000000..ace6dad990 --- /dev/null +++ b/knife/spec/unit/knife/key_show_spec.rb @@ -0,0 +1,126 @@ +# +# Author:: Tyler Cloke (<tyler@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 "chef/knife/user_key_show" +require "chef/knife/client_key_show" +require "chef/knife/key_show" +require "chef/key" + +describe "key show commands that inherit knife" do + shared_examples_for "a key show command" do + let(:stderr) { StringIO.new } + let(:params) { [] } + let(:service_object) { instance_double(Chef::Knife::KeyShow) } + let(:command) do + c = described_class.new([]) + c.ui.config[:disable_editing] = true + allow(c.ui).to receive(:stderr).and_return(stderr) + allow(c.ui).to receive(:stdout).and_return(stderr) + allow(c).to receive(:show_usage) + c + end + + context "after apply_params! is called with valid args" do + let(:params) { %w{charmander charmander-key} } + before do + command.apply_params!(params) + end + + context "when the service object is called" do + it "creates a new instance of Chef::Knife::KeyShow with the correct args" do + expect(Chef::Knife::KeyShow).to receive(:new) + .with("charmander-key", "charmander", command.load_method, command.ui) + .and_return(service_object) + command.service_object + end + end # when the service object is called + end # after apply_params! is called with valid args + end # a key show command + + describe Chef::Knife::UserKeyShow do + it_should_behave_like "a key show command" + # defined in key_helpers.rb + it_should_behave_like "a knife key command with a keyname as the second arg" + it_should_behave_like "a knife key command" do + let(:service_object) { instance_double(Chef::Knife::KeyShow) } + let(:params) { %w{charmander charmander-key} } + end + end + + describe Chef::Knife::ClientKeyShow do + it_should_behave_like "a key show command" + # defined in key_helpers.rb + it_should_behave_like "a knife key command with a keyname as the second arg" + it_should_behave_like "a knife key command" do + let(:service_object) { instance_double(Chef::Knife::KeyShow) } + let(:params) { %w{charmander charmander-key} } + end + end +end + +describe Chef::Knife::KeyShow do + let(:actor) { "charmander" } + let(:keyname) { "charmander" } + let(:ui) { instance_double("Chef::Knife::UI") } + let(:expected_hash) do + { + actor_field_name => "charmander", + "name" => "charmander-key", + "public_key" => "some-public-key", + "expiration_date" => "infinity", + } + end + + shared_examples_for "key show run command" do + let(:key_show_object) do + described_class.new(keyname, actor, load_method, ui) + end + + before do + allow(key_show_object).to receive(:display_output) + allow(Chef::Key).to receive(load_method).and_return(Chef::Key.from_hash(expected_hash)) + end + + context "when the command is run" do + it "loads the key using the proper method and args" do + expect(Chef::Key).to receive(load_method).with(actor, keyname) + key_show_object.run + end + + it "displays the key" do + expect(key_show_object).to receive(:display_output) + key_show_object.run + end + end + end + + context "when load_method is :load_by_user" do + it_should_behave_like "key show run command" do + let(:load_method) { :load_by_user } + let(:actor_field_name) { "user" } + end + end + + context "when load_method is :load_by_client" do + it_should_behave_like "key show run command" do + let(:load_method) { :load_by_client } + let(:actor_field_name) { "user" } + end + end +end diff --git a/knife/spec/unit/knife/node_bulk_delete_spec.rb b/knife/spec/unit/knife/node_bulk_delete_spec.rb new file mode 100644 index 0000000000..cf38d542fa --- /dev/null +++ b/knife/spec/unit/knife/node_bulk_delete_spec.rb @@ -0,0 +1,94 @@ +# +# Author:: Adam Jacob (<adam@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" + +describe Chef::Knife::NodeBulkDelete do + before(:each) do + Chef::Log.logger = Logger.new(StringIO.new) + + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::NodeBulkDelete.new + @knife.name_args = ["."] + @stdout = StringIO.new + allow(@knife.ui).to receive(:stdout).and_return(@stdout) + allow(@knife.ui).to receive(:confirm).and_return(true) + @nodes = {} + %w{adam brent jacob}.each do |node_name| + @nodes[node_name] = "http://localhost:4000/nodes/#{node_name}" + end + end + + describe "when creating the list of nodes" do + it "fetches the node list" do + expected = @nodes.inject({}) do |inflatedish, (name, uri)| + inflatedish[name] = Chef::Node.new.tap { |n| n.name(name) } + inflatedish + end + expect(Chef::Node).to receive(:list).and_return(@nodes) + # I hate not having == defined for anything :( + actual = @knife.all_nodes + expect(actual.keys).to match_array(expected.keys) + expect(actual.values.map(&:name)).to match_array(%w{adam brent jacob}) + end + end + + describe "run" do + before do + @inflatedish_list = @nodes.keys.inject({}) do |nodes_by_name, name| + node = Chef::Node.new + node.name(name) + allow(node).to receive(:destroy).and_return(true) + nodes_by_name[name] = node + nodes_by_name + end + allow(@knife).to receive(:all_nodes).and_return(@inflatedish_list) + end + + it "should print the nodes you are about to delete" do + @knife.run + expect(@stdout.string).to match(/#{@knife.ui.list(@nodes.keys.sort, :columns_down)}/) + end + + it "should confirm you really want to delete them" do + expect(@knife.ui).to receive(:confirm) + @knife.run + end + + it "should delete each node" do + @inflatedish_list.each_value do |n| + expect(n).to receive(:destroy) + end + @knife.run + end + + it "should only delete nodes that match the regex" do + @knife.name_args = ["adam"] + expect(@inflatedish_list["adam"]).to receive(:destroy) + expect(@inflatedish_list["brent"]).not_to receive(:destroy) + expect(@inflatedish_list["jacob"]).not_to receive(:destroy) + @knife.run + end + + it "should exit if the regex is not provided" do + @knife.name_args = [] + expect { @knife.run }.to raise_error(SystemExit) + end + + end +end diff --git a/knife/spec/unit/knife/node_delete_spec.rb b/knife/spec/unit/knife/node_delete_spec.rb new file mode 100644 index 0000000000..92932c0b6f --- /dev/null +++ b/knife/spec/unit/knife/node_delete_spec.rb @@ -0,0 +1,77 @@ +# +# Author:: Adam Jacob (<adam@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" + +describe Chef::Knife::NodeDelete do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::NodeDelete.new + @knife.config = { + print_after: nil, + } + @knife.name_args = %w{ adam ben } + allow(@knife).to receive(:output).and_return(true) + allow(@knife).to receive(:confirm).and_return(true) + + @adam_node = Chef::Node.new + @ben_node = Chef::Node.new + allow(@ben_node).to receive(:destroy).and_return(true) + allow(@adam_node).to receive(:destroy).and_return(true) + allow(Chef::Node).to receive(:load).with("adam").and_return(@adam_node) + allow(Chef::Node).to receive(:load).with("ben").and_return(@ben_node) + + @stdout = StringIO.new + allow(@knife.ui).to receive(:stdout).and_return(@stdout) + end + + describe "run" do + it "should confirm that you want to delete" do + expect(@knife).to receive(:confirm) + @knife.run + end + + it "should load the nodes" do + expect(Chef::Node).to receive(:load).with("adam").and_return(@adam_node) + expect(Chef::Node).to receive(:load).with("ben").and_return(@ben_node) + @knife.run + end + + it "should delete the nodes" do + expect(@adam_node).to receive(:destroy).and_return(@adam_node) + expect(@ben_node).to receive(:destroy).and_return(@ben_node) + @knife.run + end + + it "should not print the node" do + expect(@knife).not_to receive(:output).with("poop") + @knife.run + end + + describe "with -p or --print-after" do + it "should pretty print the node, formatted for display" do + @knife.config[:print_after] = true + expect(@knife).to receive(:format_for_display).with(@adam_node).and_return("adam") + expect(@knife).to receive(:format_for_display).with(@ben_node).and_return("ben") + expect(@knife).to receive(:output).with("adam") + expect(@knife).to receive(:output).with("ben") + @knife.run + end + end + end +end diff --git a/knife/spec/unit/knife/node_edit_spec.rb b/knife/spec/unit/knife/node_edit_spec.rb new file mode 100644 index 0000000000..e89322d415 --- /dev/null +++ b/knife/spec/unit/knife/node_edit_spec.rb @@ -0,0 +1,116 @@ +# +# Author:: Adam Jacob (<adam@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" +Chef::Knife::NodeEdit.load_deps + +describe Chef::Knife::NodeEdit do + + # helper to convert the view from Chef objects into Ruby objects representing JSON + def deserialized_json_view + Chef::JSONCompat.from_json(Chef::JSONCompat.to_json_pretty(@knife.node_editor.send(:view))) + end + + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::NodeEdit.new + @knife.config = { + editor: "cat", + attribute: nil, + print_after: nil, + } + @knife.name_args = [ "adam" ] + @node = Chef::Node.new + end + + it "should load the node" do + expect(Chef::Node).to receive(:load).with("adam").and_return(@node) + @knife.node + end + + describe "after loading the node" do + before do + @knife.config[:all_attributes] = false + + allow(@knife).to receive(:node).and_return(@node) + @node.automatic_attrs = { go: :away } + @node.default_attrs = { hide: :me } + @node.override_attrs = { dont: :show } + @node.normal_attrs = { do_show: :these } + @node.chef_environment("prod") + @node.run_list("recipe[foo]") + end + + it "creates a view of the node without attributes from roles or ohai" do + actual = deserialized_json_view + expect(actual).not_to have_key("automatic") + expect(actual).not_to have_key("override") + expect(actual).not_to have_key("default") + expect(actual["normal"]).to eq({ "do_show" => "these" }) + expect(actual["run_list"]).to eq(["recipe[foo]"]) + expect(actual["chef_environment"]).to eq("prod") + end + + it "shows the extra attributes when given the --all option" do + @knife.config[:all_attributes] = true + + actual = deserialized_json_view + expect(actual["automatic"]).to eq({ "go" => "away" }) + expect(actual["override"]).to eq({ "dont" => "show" }) + expect(actual["default"]).to eq({ "hide" => "me" }) + expect(actual["normal"]).to eq({ "do_show" => "these" }) + expect(actual["run_list"]).to eq(["recipe[foo]"]) + expect(actual["chef_environment"]).to eq("prod") + end + + it "does not consider unedited data updated" do + view = deserialized_json_view + @knife.node_editor.send(:apply_updates, view) + expect(@knife.node_editor).not_to be_updated + end + + it "considers edited data updated" do + view = deserialized_json_view + view["run_list"] << "role[fuuu]" + @knife.node_editor.send(:apply_updates, view) + expect(@knife.node_editor).to be_updated + end + + end + + describe "edit_node" do + + before do + allow(@knife).to receive(:node).and_return(@node) + end + + let(:subject) { @knife.node_editor.edit_node } + + it "raises an exception when editing is disabled" do + @knife.config[:disable_editing] = true + expect { subject }.to raise_error(SystemExit) + end + + it "raises an exception when the editor is not set" do + @knife.config[:editor] = nil + expect { subject }.to raise_error(SystemExit) + end + + end + +end diff --git a/knife/spec/unit/knife/node_environment_set_spec.rb b/knife/spec/unit/knife/node_environment_set_spec.rb new file mode 100644 index 0000000000..c2d55d0ab1 --- /dev/null +++ b/knife/spec/unit/knife/node_environment_set_spec.rb @@ -0,0 +1,61 @@ +# +# Author:: Jimmy McCrory (<jimmy.mccrory@gmail.com>) +# Copyright:: Copyright 2014-2016, Jimmy McCrory +# 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" + +describe Chef::Knife::NodeEnvironmentSet do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::NodeEnvironmentSet.new + @knife.name_args = %w{adam bar} + allow(@knife).to receive(:output).and_return(true) + @node = Chef::Node.new + @node.name("knifetest-node") + @node.chef_environment << "foo" + allow(@node).to receive(:save).and_return(true) + allow(Chef::Node).to receive(:load).and_return(@node) + end + + describe "run" do + it "should load the node" do + expect(Chef::Node).to receive(:load).with("adam") + @knife.run + end + + it "should update the environment" do + @knife.run + expect(@node.chef_environment).to eq("bar") + end + + it "should save the node" do + expect(@node).to receive(:save) + @knife.run + end + + it "sets the environment to config for display" do + @knife.run + expect(@knife.config[:environment]).to eq("bar") + end + + it "should print the environment" do + expect(@knife).to receive(:output).and_return(true) + @knife.run + end + + end +end diff --git a/knife/spec/unit/knife/node_from_file_spec.rb b/knife/spec/unit/knife/node_from_file_spec.rb new file mode 100644 index 0000000000..359b9726b6 --- /dev/null +++ b/knife/spec/unit/knife/node_from_file_spec.rb @@ -0,0 +1,59 @@ +# +# Author:: Adam Jacob (<adam@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" + +Chef::Knife::NodeFromFile.load_deps + +describe Chef::Knife::NodeFromFile do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::NodeFromFile.new + @knife.config = { + print_after: nil, + } + @knife.name_args = [ "adam.rb" ] + allow(@knife).to receive(:output).and_return(true) + allow(@knife).to receive(:confirm).and_return(true) + @node = Chef::Node.new + allow(@node).to receive(:save) + allow(@knife.loader).to receive(:load_from).and_return(@node) + @stdout = StringIO.new + allow(@knife.ui).to receive(:stdout).and_return(@stdout) + end + + describe "run" do + it "should load from a file" do + expect(@knife.loader).to receive(:load_from).with("nodes", "adam.rb").and_return(@node) + @knife.run + end + + it "should not print the Node" do + expect(@knife).not_to receive(:output) + @knife.run + end + + describe "with -p or --print-after" do + it "should print the Node" do + @knife.config[:print_after] = true + expect(@knife).to receive(:output) + @knife.run + end + end + end +end diff --git a/knife/spec/unit/knife/node_list_spec.rb b/knife/spec/unit/knife/node_list_spec.rb new file mode 100644 index 0000000000..baa79cb81f --- /dev/null +++ b/knife/spec/unit/knife/node_list_spec.rb @@ -0,0 +1,62 @@ +# +# Author:: Adam Jacob (<adam@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" + +describe Chef::Knife::NodeList do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + Chef::Config[:environment] = nil # reset this value each time, as it is not reloaded + @knife = Chef::Knife::NodeList.new + allow(@knife).to receive(:output).and_return(true) + @list = { + "foo" => "http://example.com/foo", + "bar" => "http://example.com/foo", + } + allow(Chef::Node).to receive(:list).and_return(@list) + allow(Chef::Node).to receive(:list_by_environment).and_return(@list) + end + + describe "run" do + it "should list all of the nodes if -E is not specified" do + expect(Chef::Node).to receive(:list).and_return(@list) + @knife.run + end + + it "should pretty print the list" do + expect(Chef::Node).to receive(:list).and_return(@list) + expect(@knife).to receive(:output).with(%w{bar foo}) + @knife.run + end + + it "should list nodes in the specific environment if -E ENVIRONMENT is specified" do + Chef::Config[:environment] = "prod" + expect(Chef::Node).to receive(:list_by_environment).with("prod").and_return(@list) + @knife.run + end + + describe "with -w or --with-uri" do + it "should pretty print the hash" do + @knife.config[:with_uri] = true + expect(Chef::Node).to receive(:list).and_return(@list) + expect(@knife).to receive(:output).with(@list) + @knife.run + end + end + end +end diff --git a/knife/spec/unit/knife/node_policy_set_spec.rb b/knife/spec/unit/knife/node_policy_set_spec.rb new file mode 100644 index 0000000000..5815da29df --- /dev/null +++ b/knife/spec/unit/knife/node_policy_set_spec.rb @@ -0,0 +1,122 @@ +# +# Author:: Piyush Awasthi (<piyush.awasthi@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" + +describe Chef::Knife::NodePolicySet do + let(:node) do + node = Chef::Node.new + node.name("adam") + node.run_list = ["role[base]"] + node + end + + let(:knife) do + Chef::Log.logger = Logger.new(StringIO.new) + Chef::Config[:knife][:bootstrap_template] = bootstrap_template unless bootstrap_template.nil? + knife_obj = Chef::Knife::NodePolicySet.new(bootstrap_cli_options) + knife_obj.merge_configs + allow(knife_obj.ui).to receive(:stderr).and_return(stderr) + allow(knife_obj).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false) + knife_obj + end + + let(:stderr) { StringIO.new } + let(:bootstrap_template) { nil } + let(:bootstrap_cli_options) { [ ] } + + describe "#run" do + context "when node_name is not given" do + let(:bootstrap_cli_options) { %w{ } } + it "returns an error that you must specify a node name" do + expect { knife.send(:validate_node!) }.to raise_error(SystemExit) + expect(stderr.string).to include("ERROR: You must specify a node name") + end + end + + context "when node is given" do + let(:bootstrap_cli_options) { %w{ adam staging my-app } } + it "should load the node" do + expect(Chef::Node).to receive(:load).with(bootstrap_cli_options[0]).and_return(node) + allow(node).to receive(:save).and_return(true) + knife.run + end + end + + context "when node not saved" do + let(:bootstrap_cli_options) { %w{ adam staging my-app } } + it "returns an error node not updated successfully" do + allow(Chef::Node).to receive(:load).with(bootstrap_cli_options[0]).and_return(node) + allow(node).to receive(:save).and_return(false) + knife.run + expect(stderr.string.strip).to eq("Error in updating node #{node.name}") + end + end + + context "when the policy is set successfully on the node" do + let(:bootstrap_cli_options) { %w{ adam staging my-app } } + it "returns node updated successfully" do + allow(Chef::Node).to receive(:load).with(bootstrap_cli_options[0]).and_return(node) + allow(node).to receive(:save).and_return(true) + knife.run + expect(stderr.string.strip).to eq("Successfully set the policy on node #{node.name}") + end + end + end + + describe "handling policy options" do + context "when policy_group and policy_name is not given" do + let(:bootstrap_cli_options) { %w{ } } + it "returns an error stating that policy_name and policy_group must be given together" do + expect { knife.send(:validate_options!) }.to raise_error(SystemExit) + expect(stderr.string).to include("ERROR: Policy group and name must be specified together") + end + end + + context "when only policy_name is given" do + let(:bootstrap_cli_options) { %w{ adam staging } } + it "returns an error stating that policy_name and policy_group must be given together" do + expect { knife.send(:validate_options!) }.to raise_error(SystemExit) + expect(stderr.string).to include("ERROR: Policy group and name must be specified together") + end + end + + context "when only policy_group is given" do + let(:bootstrap_cli_options) { %w{ adam my-app } } + it "returns an error stating that policy_name and policy_group must be given together" do + expect { knife.send(:validate_options!) }.to raise_error(SystemExit) + expect(stderr.string).to include("ERROR: Policy group and name must be specified together") + end + end + + context "when policy_name and policy_group are given with no conflicting options" do + let(:bootstrap_cli_options) { %w{ adam staging my-app } } + it "passes options validation" do + expect { knife.send(:validate_options!) }.to_not raise_error + end + + it "returns value set in config" do + allow(Chef::Node).to receive(:load).with(bootstrap_cli_options[0]).and_return(node) + allow(node).to receive(:save).and_return(false) + knife.run + expect(node.policy_name).to eq("my-app") + expect(node.policy_group).to eq("staging") + end + end + end +end diff --git a/knife/spec/unit/knife/node_run_list_add_spec.rb b/knife/spec/unit/knife/node_run_list_add_spec.rb new file mode 100644 index 0000000000..87b75d9818 --- /dev/null +++ b/knife/spec/unit/knife/node_run_list_add_spec.rb @@ -0,0 +1,145 @@ +# +# Author:: Adam Jacob (<adam@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" + +describe Chef::Knife::NodeRunListAdd do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::NodeRunListAdd.new + @knife.config = { + after: nil, + } + @knife.name_args = [ "adam", "role[monkey]" ] + allow(@knife).to receive(:output).and_return(true) + @node = Chef::Node.new + allow(@node).to receive(:save).and_return(true) + allow(Chef::Node).to receive(:load).and_return(@node) + end + + describe "run" do + it "should load the node" do + expect(Chef::Node).to receive(:load).with("adam") + @knife.run + end + + it "should add to the run list" do + @knife.run + expect(@node.run_list[0]).to eq("role[monkey]") + end + + it "should save the node" do + expect(@node).to receive(:save) + @knife.run + end + + it "should print the run list" do + expect(@knife).to receive(:output).and_return(true) + @knife.run + end + + describe "with -a or --after specified" do + it "should add to the run list after the specified entry" do + @node.run_list << "role[acorns]" + @node.run_list << "role[barn]" + @knife.config[:after] = "role[acorns]" + @knife.run + expect(@node.run_list[0]).to eq("role[acorns]") + expect(@node.run_list[1]).to eq("role[monkey]") + expect(@node.run_list[2]).to eq("role[barn]") + end + end + + describe "with -b or --before specified" do + it "should add to the run list before the specified entry" do + @node.run_list << "role[acorns]" + @node.run_list << "role[barn]" + @knife.config[:before] = "role[acorns]" + @knife.run + expect(@node.run_list[0]).to eq("role[monkey]") + expect(@node.run_list[1]).to eq("role[acorns]") + expect(@node.run_list[2]).to eq("role[barn]") + end + end + + describe "with both --after and --before specified" do + it "exits with an error" do + @node.run_list << "role[acorns]" + @node.run_list << "role[barn]" + @knife.config[:before] = "role[acorns]" + @knife.config[:after] = "role[acorns]" + expect(@knife.ui).to receive(:fatal) + expect { @knife.run }.to raise_error(SystemExit) + end + end + + describe "with more than one role or recipe" do + it "should add to the run list all the entries" do + @knife.name_args = [ "adam", "role[monkey],role[duck]" ] + @node.run_list << "role[acorns]" + @knife.run + expect(@node.run_list[0]).to eq("role[acorns]") + expect(@node.run_list[1]).to eq("role[monkey]") + expect(@node.run_list[2]).to eq("role[duck]") + end + end + + describe "with more than one role or recipe with space between items" do + it "should add to the run list all the entries" do + @knife.name_args = [ "adam", "role[monkey], role[duck]" ] + @node.run_list << "role[acorns]" + @knife.run + expect(@node.run_list[0]).to eq("role[acorns]") + expect(@node.run_list[1]).to eq("role[monkey]") + expect(@node.run_list[2]).to eq("role[duck]") + end + end + + describe "with more than one role or recipe as different arguments" do + it "should add to the run list all the entries" do + @knife.name_args = [ "adam", "role[monkey]", "role[duck]" ] + @node.run_list << "role[acorns]" + @knife.run + expect(@node.run_list[0]).to eq("role[acorns]") + expect(@node.run_list[1]).to eq("role[monkey]") + expect(@node.run_list[2]).to eq("role[duck]") + end + end + + describe "with more than one role or recipe as different arguments and list separated by commas" do + it "should add to the run list all the entries" do + @knife.name_args = [ "adam", "role[monkey]", "role[duck],recipe[bird::fly]" ] + @node.run_list << "role[acorns]" + @knife.run + expect(@node.run_list[0]).to eq("role[acorns]") + expect(@node.run_list[1]).to eq("role[monkey]") + expect(@node.run_list[2]).to eq("role[duck]") + end + end + + describe "with one role or recipe but with an extraneous comma" do + it "should add to the run list one item" do + @knife.name_args = [ "adam", "role[monkey]," ] + @node.run_list << "role[acorns]" + @knife.run + expect(@node.run_list[0]).to eq("role[acorns]") + expect(@node.run_list[1]).to eq("role[monkey]") + end + end + end +end diff --git a/knife/spec/unit/knife/node_run_list_remove_spec.rb b/knife/spec/unit/knife/node_run_list_remove_spec.rb new file mode 100644 index 0000000000..0eff7c6d27 --- /dev/null +++ b/knife/spec/unit/knife/node_run_list_remove_spec.rb @@ -0,0 +1,106 @@ +# +# Author:: Adam Jacob (<adam@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" + +describe Chef::Knife::NodeRunListRemove do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::NodeRunListRemove.new + @knife.config[:print_after] = nil + @knife.name_args = [ "adam", "role[monkey]" ] + @node = Chef::Node.new + @node.name("knifetest-node") + @node.run_list << "role[monkey]" + allow(@node).to receive(:save).and_return(true) + + allow(@knife.ui).to receive(:output).and_return(true) + allow(@knife.ui).to receive(:confirm).and_return(true) + + allow(Chef::Node).to receive(:load).and_return(@node) + end + + describe "run" do + it "should load the node" do + expect(Chef::Node).to receive(:load).with("adam").and_return(@node) + @knife.run + end + + it "should remove the item from the run list" do + @knife.run + expect(@node.run_list[0]).not_to eq("role[monkey]") + end + + it "should save the node" do + expect(@node).to receive(:save).and_return(true) + @knife.run + end + + it "should print the run list" do + @knife.config[:print_after] = true + expect(@knife.ui).to receive(:output).with({ "knifetest-node" => { "run_list" => [] } }) + @knife.run + end + + describe "run with a list of roles and recipes" do + it "should remove the items from the run list" do + @node.run_list << "role[monkey]" + @node.run_list << "recipe[duck::type]" + @knife.name_args = [ "adam", "role[monkey],recipe[duck::type]" ] + @knife.run + expect(@node.run_list).not_to include("role[monkey]") + expect(@node.run_list).not_to include("recipe[duck::type]") + end + + it "should remove the items from the run list when name args contains whitespace" do + @node.run_list << "role[monkey]" + @node.run_list << "recipe[duck::type]" + @knife.name_args = [ "adam", "role[monkey], recipe[duck::type]" ] + @knife.run + expect(@node.run_list).not_to include("role[monkey]") + expect(@node.run_list).not_to include("recipe[duck::type]") + end + + it "should remove the items from the run list when name args contains multiple run lists" do + @node.run_list << "role[blah]" + @node.run_list << "recipe[duck::type]" + @knife.name_args = [ "adam", "role[monkey], recipe[duck::type]", "role[blah]" ] + @knife.run + expect(@node.run_list).not_to include("role[monkey]") + expect(@node.run_list).not_to include("recipe[duck::type]") + end + + it "should warn when the thing to remove is not in the runlist" do + @node.run_list << "role[blah]" + @node.run_list << "recipe[duck::type]" + @knife.name_args = [ "adam", "role[blork]" ] + expect(@knife.ui).to receive(:warn).with("role[blork] is not in the run list") + @knife.run + end + + it "should warn even more when the thing to remove is not in the runlist and unqualified" do + @node.run_list << "role[blah]" + @node.run_list << "recipe[duck::type]" + @knife.name_args = %w{adam blork} + expect(@knife.ui).to receive(:warn).with("blork is not in the run list") + expect(@knife.ui).to receive(:warn).with(/did you forget recipe\[\] or role\[\]/) + @knife.run + end + end + end +end diff --git a/knife/spec/unit/knife/node_run_list_set_spec.rb b/knife/spec/unit/knife/node_run_list_set_spec.rb new file mode 100644 index 0000000000..35fdd63e4d --- /dev/null +++ b/knife/spec/unit/knife/node_run_list_set_spec.rb @@ -0,0 +1,115 @@ +# +# Author:: Mike Fiedler (<miketheman@gmail.com>) +# Copyright:: Copyright 2013-2016, Mike Fiedler +# 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" + +describe Chef::Knife::NodeRunListSet do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::NodeRunListSet.new + @knife.config = {} + @knife.name_args = [ "adam", "role[monkey]" ] + allow(@knife).to receive(:output).and_return(true) + @node = Chef::Node.new + allow(@node).to receive(:save).and_return(true) + allow(Chef::Node).to receive(:load).and_return(@node) + end + + describe "run" do + it "should load the node" do + expect(Chef::Node).to receive(:load).with("adam") + @knife.run + end + + it "should set the run list" do + @knife.run + expect(@node.run_list[0]).to eq("role[monkey]") + end + + it "should save the node" do + expect(@node).to receive(:save) + @knife.run + end + + it "should print the run list" do + expect(@knife).to receive(:output).and_return(true) + @knife.run + end + + describe "with more than one role or recipe" do + it "should set the run list to all the entries" do + @knife.name_args = [ "adam", "role[monkey],role[duck]" ] + @knife.run + expect(@node.run_list[0]).to eq("role[monkey]") + expect(@node.run_list[1]).to eq("role[duck]") + end + end + + describe "with more than one role or recipe with space between items" do + it "should set the run list to all the entries" do + @knife.name_args = [ "adam", "role[monkey], role[duck]" ] + @knife.run + expect(@node.run_list[0]).to eq("role[monkey]") + expect(@node.run_list[1]).to eq("role[duck]") + end + end + + describe "with more than one role or recipe as different arguments" do + it "should set the run list to all the entries" do + @knife.name_args = [ "adam", "role[monkey]", "role[duck]" ] + @knife.run + expect(@node.run_list[0]).to eq("role[monkey]") + expect(@node.run_list[1]).to eq("role[duck]") + end + end + + describe "with more than one role or recipe as different arguments and list separated by comas" do + it "should add to the run list all the entries" do + @knife.name_args = [ "adam", "role[monkey]", "role[duck],recipe[bird::fly]" ] + @knife.run + expect(@node.run_list[0]).to eq("role[monkey]") + expect(@node.run_list[1]).to eq("role[duck]") + end + end + + describe "with one role or recipe but with an extraneous comma" do + it "should add to the run list one item" do + @knife.name_args = [ "adam", "role[monkey]," ] + @knife.run + expect(@node.run_list[0]).to eq("role[monkey]") + end + end + + describe "with an existing run list" do + it "should overwrite any existing run list items" do + @node.run_list << "role[acorns]" + @node.run_list << "role[zebras]" + expect(@node.run_list[0]).to eq("role[acorns]") + expect(@node.run_list[1]).to eq("role[zebras]") + expect(@node.run_list.run_list_items.size).to eq(2) + + @knife.name_args = [ "adam", "role[monkey]", "role[duck]" ] + @knife.run + expect(@node.run_list[0]).to eq("role[monkey]") + expect(@node.run_list[1]).to eq("role[duck]") + expect(@node.run_list.run_list_items.size).to eq(2) + end + end + + end +end diff --git a/knife/spec/unit/knife/node_show_spec.rb b/knife/spec/unit/knife/node_show_spec.rb new file mode 100644 index 0000000000..c26ae94f40 --- /dev/null +++ b/knife/spec/unit/knife/node_show_spec.rb @@ -0,0 +1,65 @@ +# +# Author:: Adam Jacob (<adam@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" + +describe Chef::Knife::NodeShow do + + let(:node) do + node = Chef::Node.new + node.name("adam") + node.run_list = ["role[base]"] + node + end + + let(:knife) do + knife = Chef::Knife::NodeShow.new + knife.name_args = [ "adam" ] + knife + end + + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + end + + describe "run" do + it "should load the node" do + expect(Chef::Node).to receive(:load).with("adam").and_return(node) + allow(knife).to receive(:output).and_return(true) + knife.run + end + + it "should pretty print the node, formatted for display" do + knife.config[:format] = nil + stdout = StringIO.new + allow(knife.ui).to receive(:stdout).and_return(stdout) + allow(Chef::Node).to receive(:load).and_return(node) + knife.run + expect(stdout.string).to eql("Node Name: adam\nEnvironment: _default\nFQDN: \nIP: \nRun List: \nRoles: \nRecipes: \nPlatform: \nTags: \n") + end + + it "should pretty print json" do + knife.config[:format] = "json" + stdout = StringIO.new + allow(knife.ui).to receive(:stdout).and_return(stdout) + expect(Chef::Node).to receive(:load).with("adam").and_return(node) + knife.run + expect(stdout.string).to eql("{\n \"name\": \"adam\",\n \"chef_environment\": \"_default\",\n \"run_list\": [\n\n]\n,\n \"normal\": {\n\n }\n}\n") + end + end +end diff --git a/knife/spec/unit/knife/org_create_spec.rb b/knife/spec/unit/knife/org_create_spec.rb new file mode 100644 index 0000000000..f45ade2df7 --- /dev/null +++ b/knife/spec/unit/knife/org_create_spec.rb @@ -0,0 +1,76 @@ +# +# Copyright:: Copyright 2014-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 "knife_spec_helper" +require "chef/org" + +describe Chef::Knife::OrgCreate do + before :each do + Chef::Knife::OrgCreate.load_deps + @knife = Chef::Knife::OrgCreate.new + @org = double("Chef::Org") + allow(Chef::Org).to receive(:new).and_return(@org) + @key = "You don't come into cooking to get rich - Ramsay" + allow(@org).to receive(:private_key).and_return(@key) + @org_name = "ss" + @org_full_name = "secretsauce" + end + + let(:org_args) do + { + name: @org_name, + full_name: @org_full_name, + } + end + + describe "with no org_name and org_fullname" do + it "fails with an informative message" do + expect(@knife.ui).to receive(:fatal).with("You must specify an ORG_NAME and an ORG_FULL_NAME") + expect(@knife).to receive(:show_usage) + expect { @knife.run }.to raise_error(SystemExit) + end + end + + describe "with org_name and org_fullname" do + before :each do + @knife.name_args << @org_name << @org_full_name + end + + it "creates an org" do + expect(@org).to receive(:create).and_return(@org) + expect(@org).to receive(:full_name).with("secretsauce") + expect(@knife.ui).to receive(:msg).with(@key) + @knife.run + end + + context "with --assocation-user" do + before :each do + @knife.config[:association_user] = "ramsay" + end + + it "creates an org, associates a user, and adds it to the admins group" do + expect(@org).to receive(:full_name).with("secretsauce") + expect(@org).to receive(:create).and_return(@org) + expect(@org).to receive(:associate_user).with("ramsay") + expect(@org).to receive(:add_user_to_group).with("admins", "ramsay") + expect(@org).to receive(:add_user_to_group).with("billing-admins", "ramsay") + expect(@knife.ui).to receive(:msg).with(@key) + @knife.run + end + end + end +end diff --git a/knife/spec/unit/knife/org_delete_spec.rb b/knife/spec/unit/knife/org_delete_spec.rb new file mode 100644 index 0000000000..33311bd678 --- /dev/null +++ b/knife/spec/unit/knife/org_delete_spec.rb @@ -0,0 +1,41 @@ +# +# Author:: Snehal Dwivedi (<sdwivedi@msystechnologies.com>) +# 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 "chef/org" + +describe Chef::Knife::OrgDelete do + + let(:root_rest) { double("Chef::ServerAPI") } + + before :each do + @knife = Chef::Knife::OrgDelete.new + @org_name = "foobar" + @org_full_name = "secretsauce" + @knife.name_args << @org_name + @org = double("Chef::Org") + end + + it "should confirm that you want to delete and then delete organizations" do + expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_url]).and_return(root_rest) + expect(@knife.ui).to receive(:confirm).with("Do you want to delete the organization #{@org_name}") + expect(root_rest).to receive(:delete).with("organizations/#{@org_name}") + expect(@knife.ui).to receive(:output) + @knife.run + end +end diff --git a/knife/spec/unit/knife/org_edit_spec.rb b/knife/spec/unit/knife/org_edit_spec.rb new file mode 100644 index 0000000000..af9dae2c49 --- /dev/null +++ b/knife/spec/unit/knife/org_edit_spec.rb @@ -0,0 +1,49 @@ +# +# Author:: Snehal Dwivedi (<sdwivedi@msystechnologies.com>) +# 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" + +describe Chef::Knife::OrgEdit do + let(:knife) { Chef::Knife::OrgEdit.new } + let(:root_rest) { double("Chef::ServerAPI") } + + before :each do + Chef::Knife::OrgEdit.load_deps + @org_name = "foobar" + knife.name_args << @org_name + @org = double("Chef::Org") + knife.config[:disable_editing] = true + end + + it "loads and edits the organisation" do + expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest) + original_data = { "org_name" => "my_org" } + data = { "org_name" => "my_org1" } + expect(root_rest).to receive(:get).with("organizations/foobar").and_return(original_data) + expect(knife).to receive(:edit_hash).with(original_data).and_return(data) + expect(root_rest).to receive(:put).with("organizations/foobar", data) + knife.run + end + + it "prints usage and exits when a org name is not provided" do + knife.name_args = [] + expect(knife).to receive(:show_usage) + expect(knife.ui).to receive(:fatal) + expect { knife.run }.to raise_error(SystemExit) + end +end diff --git a/knife/spec/unit/knife/org_list_spec.rb b/knife/spec/unit/knife/org_list_spec.rb new file mode 100644 index 0000000000..aa5fca5099 --- /dev/null +++ b/knife/spec/unit/knife/org_list_spec.rb @@ -0,0 +1,58 @@ +# +# 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 "chef/org" + +describe Chef::Knife::OrgList do + + let(:root_rest) { double("Chef::ServerAPI") } + + let(:orgs) do + { + "org1" => "first", + "org2" => "second", + "hiddenhiddenhiddenhi" => "hidden", + } + end + + before :each do + @org = double("Chef::Org") + @knife = Chef::Knife::OrgList.new + expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest) + allow(root_rest).to receive(:get).with("organizations").and_return(orgs) + end + + describe "with no arguments" do + it "lists all non hidden orgs" do + expect(@knife.ui).to receive(:output).with(%w{org1 org2}) + @knife.run + end + + end + + describe "with all_orgs argument" do + before do + @knife.config[:all_orgs] = true + end + + it "lists all orgs including hidden orgs" do + expect(@knife.ui).to receive(:output).with(%w{hiddenhiddenhiddenhi org1 org2}) + @knife.run + end + end +end diff --git a/knife/spec/unit/knife/org_show_spec.rb b/knife/spec/unit/knife/org_show_spec.rb new file mode 100644 index 0000000000..364b879a7c --- /dev/null +++ b/knife/spec/unit/knife/org_show_spec.rb @@ -0,0 +1,45 @@ +# +# Author:: Snehal Dwivedi (<sdwivedi@msystechnologies.com>) +# 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 "chef/org" + +describe Chef::Knife::OrgShow do + + let(:root_rest) { double("Chef::ServerAPI") } + + before :each do + @knife = Chef::Knife::OrgShow.new + @org_name = "foobar" + @knife.name_args << @org_name + @org = double("Chef::Org") + expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest) + allow(@org).to receive(:root_rest).and_return(root_rest) + end + + it "should load the organisation" do + expect(root_rest).to receive(:get).with("organizations/#{@org_name}") + @knife.run + end + + it "should pretty print the output organisation" do + expect(root_rest).to receive(:get).with("organizations/#{@org_name}") + expect(@knife.ui).to receive(:output) + @knife.run + end +end diff --git a/knife/spec/unit/knife/org_user_add_spec.rb b/knife/spec/unit/knife/org_user_add_spec.rb new file mode 100644 index 0000000000..72ee1d0607 --- /dev/null +++ b/knife/spec/unit/knife/org_user_add_spec.rb @@ -0,0 +1,39 @@ +# +# 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 "chef/org" + +describe Chef::Knife::OrgUserAdd do + context "with --admin" do + subject(:knife) { Chef::Knife::OrgUserAdd.new } + let(:org) { double("Chef::Org") } + + it "adds the user to admins and billing-admins groups" do + allow(Chef::Org).to receive(:new).and_return(org) + + knife.config[:admin] = true + knife.name_args = %w{testorg testuser} + + expect(org).to receive(:associate_user).with("testuser") + expect(org).to receive(:add_user_to_group).with("admins", "testuser") + expect(org).to receive(:add_user_to_group).with("billing-admins", "testuser") + + knife.run + end + end +end diff --git a/knife/spec/unit/knife/raw_spec.rb b/knife/spec/unit/knife/raw_spec.rb new file mode 100644 index 0000000000..90a09a31e6 --- /dev/null +++ b/knife/spec/unit/knife/raw_spec.rb @@ -0,0 +1,43 @@ +# +# Author:: Steven Danna (<steve@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" + +describe Chef::Knife::Raw do + let(:rest) do + r = double("Chef::Knife::Raw::RawInputServerAPI") + allow(Chef::Knife::Raw::RawInputServerAPI).to receive(:new).and_return(r) + r + end + + let(:knife) do + k = Chef::Knife::Raw.new + k.config[:method] = "GET" + k.name_args = [ "/nodes" ] + k + end + + describe "run" do + it "should set the x-ops-request-source header when --proxy-auth is set" do + knife.config[:proxy_auth] = true + expect(rest).to receive(:request).with(:GET, "/nodes", + { "Content-Type" => "application/json", + "x-ops-request-source" => "web" }, false) + knife.run + end + end +end diff --git a/knife/spec/unit/knife/role_bulk_delete_spec.rb b/knife/spec/unit/knife/role_bulk_delete_spec.rb new file mode 100644 index 0000000000..f68efba57c --- /dev/null +++ b/knife/spec/unit/knife/role_bulk_delete_spec.rb @@ -0,0 +1,80 @@ +# +# Author:: Stephen Delano (<stephen@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" + +describe Chef::Knife::RoleBulkDelete do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::RoleBulkDelete.new + @knife.config = { + print_after: nil, + } + @knife.name_args = ["."] + @stdout = StringIO.new + allow(@knife.ui).to receive(:stdout).and_return(@stdout) + allow(@knife.ui).to receive(:confirm).and_return(true) + @roles = {} + %w{dev staging production}.each do |role_name| + role = Chef::Role.new + role.name(role_name) + allow(role).to receive(:destroy).and_return(true) + @roles[role_name] = role + end + allow(Chef::Role).to receive(:list).and_return(@roles) + end + + describe "run" do + + it "should get the list of the roles" do + expect(Chef::Role).to receive(:list).and_return(@roles) + @knife.run + end + + it "should print the roles you are about to delete" do + @knife.run + expect(@stdout.string).to match(/#{@knife.ui.list(@roles.keys.sort, :columns_down)}/) + end + + it "should confirm you really want to delete them" do + expect(@knife.ui).to receive(:confirm) + @knife.run + end + + it "should delete each role" do + @roles.each_value do |r| + expect(r).to receive(:destroy) + end + @knife.run + end + + it "should only delete roles that match the regex" do + @knife.name_args = ["dev"] + expect(@roles["dev"]).to receive(:destroy) + expect(@roles["staging"]).not_to receive(:destroy) + expect(@roles["production"]).not_to receive(:destroy) + @knife.run + end + + it "should exit if the regex is not provided" do + @knife.name_args = [] + expect { @knife.run }.to raise_error(SystemExit) + end + + end +end diff --git a/knife/spec/unit/knife/role_create_spec.rb b/knife/spec/unit/knife/role_create_spec.rb new file mode 100644 index 0000000000..13f47492b1 --- /dev/null +++ b/knife/spec/unit/knife/role_create_spec.rb @@ -0,0 +1,80 @@ +# +# Author:: Adam Jacob (<adam@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" + +describe Chef::Knife::RoleCreate do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::RoleCreate.new + @knife.config = { + description: nil, + } + @knife.name_args = [ "adam" ] + allow(@knife).to receive(:output).and_return(true) + @role = Chef::Role.new + allow(@role).to receive(:save) + allow(Chef::Role).to receive(:new).and_return(@role) + allow(@knife).to receive(:edit_data).and_return(@role) + @stdout = StringIO.new + allow(@knife.ui).to receive(:stdout).and_return(@stdout) + end + + describe "run" do + it "should create a new role" do + expect(Chef::Role).to receive(:new).and_return(@role) + @knife.run + end + + it "should set the role name" do + expect(@role).to receive(:name).with("adam") + @knife.run + end + + it "should not print the role" do + expect(@knife).not_to receive(:output) + @knife.run + end + + it "should allow you to edit the data" do + expect(@knife).to receive(:edit_data).with(@role, object_class: Chef::Role) + @knife.run + end + + it "should save the role" do + expect(@role).to receive(:save) + @knife.run + end + + describe "with -d or --description" do + it "should set the description" do + @knife.config[:description] = "All is bob" + expect(@role).to receive(:description).with("All is bob") + @knife.run + end + end + + describe "with -p or --print-after" do + it "should pretty print the node, formatted for display" do + @knife.config[:print_after] = true + expect(@knife).to receive(:output).with(@role) + @knife.run + end + end + end +end diff --git a/knife/spec/unit/knife/role_delete_spec.rb b/knife/spec/unit/knife/role_delete_spec.rb new file mode 100644 index 0000000000..658da5299d --- /dev/null +++ b/knife/spec/unit/knife/role_delete_spec.rb @@ -0,0 +1,67 @@ +# +# Author:: Adam Jacob (<adam@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" + +describe Chef::Knife::RoleDelete do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::RoleDelete.new + @knife.config = { + print_after: nil, + } + @knife.name_args = [ "adam" ] + allow(@knife).to receive(:output).and_return(true) + allow(@knife).to receive(:confirm).and_return(true) + @role = Chef::Role.new + allow(@role).to receive(:destroy).and_return(true) + allow(Chef::Role).to receive(:load).and_return(@role) + @stdout = StringIO.new + allow(@knife.ui).to receive(:stdout).and_return(@stdout) + end + + describe "run" do + it "should confirm that you want to delete" do + expect(@knife).to receive(:confirm) + @knife.run + end + + it "should load the Role" do + expect(Chef::Role).to receive(:load).with("adam").and_return(@role) + @knife.run + end + + it "should delete the Role" do + expect(@role).to receive(:destroy).and_return(@role) + @knife.run + end + + it "should not print the Role" do + expect(@knife).not_to receive(:output) + @knife.run + end + + describe "with -p or --print-after" do + it "should pretty print the Role, formatted for display" do + @knife.config[:print_after] = true + expect(@knife).to receive(:output) + @knife.run + end + end + end +end diff --git a/knife/spec/unit/knife/role_edit_spec.rb b/knife/spec/unit/knife/role_edit_spec.rb new file mode 100644 index 0000000000..adade177a7 --- /dev/null +++ b/knife/spec/unit/knife/role_edit_spec.rb @@ -0,0 +1,77 @@ +# +# Author:: Adam Jacob (<adam@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" + +describe Chef::Knife::RoleEdit do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::RoleEdit.new + @knife.config[:print_after] = nil + @knife.name_args = [ "adam" ] + allow(@knife.ui).to receive(:output).and_return(true) + @role = Chef::Role.new + allow(@role).to receive(:save) + allow(Chef::Role).to receive(:load).and_return(@role) + allow(@knife.ui).to receive(:edit_data).and_return(@role) + allow(@knife.ui).to receive(:msg) + end + + describe "run" do + it "should load the role" do + expect(Chef::Role).to receive(:load).with("adam").and_return(@role) + @knife.run + end + + it "should edit the role data" do + expect(@knife.ui).to receive(:edit_data).with(@role, object_class: Chef::Role) + @knife.run + end + + it "should save the edited role data" do + pansy = Chef::Role.new + + @role.name("new_role_name") + expect(@knife.ui).to receive(:edit_data).with(@role, object_class: Chef::Role).and_return(pansy) + expect(pansy).to receive(:save) + @knife.run + end + + it "should not save the unedited role data" do + pansy = Chef::Role.new + + expect(@knife.ui).to receive(:edit_data).with(@role, object_class: Chef::Role).and_return(pansy) + expect(pansy).not_to receive(:save) + @knife.run + + end + + it "should not print the role" do + expect(@knife.ui).not_to receive(:output) + @knife.run + end + + describe "with -p or --print-after" do + it "should pretty print the role, formatted for display" do + @knife.config[:print_after] = true + expect(@knife.ui).to receive(:output).with(@role) + @knife.run + end + end + end +end diff --git a/knife/spec/unit/knife/role_env_run_list_add_spec.rb b/knife/spec/unit/knife/role_env_run_list_add_spec.rb new file mode 100644 index 0000000000..b42ec6141f --- /dev/null +++ b/knife/spec/unit/knife/role_env_run_list_add_spec.rb @@ -0,0 +1,217 @@ +# +# Author:: Adam Jacob (<adam@chef.io>) +# Author:: Will Albenzi (<walbenzi@gmail.com>) +# 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" + +describe Chef::Knife::RoleEnvRunListAdd do + before(:each) do + # Chef::Config[:role_name] = "websimian" + # Chef::Config[:env_name] = "QA" + @knife = Chef::Knife::RoleEnvRunListAdd.new + @knife.config = { + after: nil, + } + @knife.name_args = [ "will", "QA", "role[monkey]" ] + allow(@knife).to receive(:output).and_return(true) + @role = Chef::Role.new + allow(@role).to receive(:save).and_return(true) + allow(Chef::Role).to receive(:load).and_return(@role) + end + + describe "run" do + + # it "should display all the things" do + # @knife.run + # @role.to_json.should == 'show all the things' + # end + + it "should have an empty default run list" do + @knife.run + expect(@role.run_list[0]).to be_nil + end + + it "should have a QA environment" do + @knife.run + expect(@role.active_run_list_for("QA")).to eq("QA") + end + + it "should load the role named will" do + expect(Chef::Role).to receive(:load).with("will") + @knife.run + end + + it "should be able to add an environment specific run list" do + @knife.run + expect(@role.run_list_for("QA")[0]).to eq("role[monkey]") + end + + it "should save the role" do + expect(@role).to receive(:save) + @knife.run + end + + it "should print the run list" do + expect(@knife).to receive(:output).and_return(true) + @knife.run + end + + describe "with -a or --after specified" do + it "should not create a change if the specified 'after' never comes" do + @role.run_list_for("_default") << "role[acorns]" + @role.run_list_for("_default") << "role[barn]" + @knife.config[:after] = "role[acorns]" + @knife.name_args = [ "will", "QA", "role[pad]" ] + @knife.run + expect(@role.run_list_for("QA")[0]).to be_nil + expect(@role.run_list[0]).to eq("role[acorns]") + expect(@role.run_list[1]).to eq("role[barn]") + expect(@role.run_list[2]).to be_nil + end + + it "should add to the run list after the specified entries in the QA run list" do + # Setup + @role.run_list_for("_default") << "role[acorns]" + @role.run_list_for("_default") << "role[barn]" + @knife.run + @role.run_list_for("QA") << "role[pencil]" + @role.run_list_for("QA") << "role[pen]" + # Configuration we are testing + @knife.config[:after] = "role[pencil]" + @knife.name_args = [ "will", "QA", "role[pad]", "role[whackadoo]" ] + @knife.run + # The actual tests + expect(@role.run_list_for("QA")[0]).to eq("role[monkey]") + expect(@role.run_list_for("QA")[1]).to eq("role[pencil]") + expect(@role.run_list_for("QA")[2]).to eq("role[pad]") + expect(@role.run_list_for("QA")[3]).to eq("role[whackadoo]") + expect(@role.run_list_for("QA")[4]).to eq("role[pen]") + expect(@role.run_list[0]).to eq("role[acorns]") + expect(@role.run_list[1]).to eq("role[barn]") + expect(@role.run_list[2]).to be_nil + end + end + + describe "with more than one role or recipe" do + it "should add to the QA run list all the entries" do + @knife.name_args = [ "will", "QA", "role[monkey],role[duck]" ] + @role.run_list_for("_default") << "role[acorns]" + @knife.run + expect(@role.run_list_for("QA")[0]).to eq("role[monkey]") + expect(@role.run_list_for("QA")[1]).to eq("role[duck]") + expect(@role.run_list[0]).to eq("role[acorns]") + expect(@role.run_list[1]).to be_nil + end + end + + describe "with more than one role or recipe with space between items" do + it "should add to the run list all the entries" do + @knife.name_args = [ "will", "QA", "role[monkey], role[duck]" ] + @role.run_list_for("_default") << "role[acorns]" + @knife.run + expect(@role.run_list_for("QA")[0]).to eq("role[monkey]") + expect(@role.run_list_for("QA")[1]).to eq("role[duck]") + expect(@role.run_list[0]).to eq("role[acorns]") + expect(@role.run_list[1]).to be_nil + end + end + + describe "with more than one role or recipe as different arguments" do + it "should add to the run list all the entries" do + @knife.name_args = [ "will", "QA", "role[monkey]", "role[duck]" ] + @role.run_list_for("_default") << "role[acorns]" + @knife.run + expect(@role.run_list_for("QA")[0]).to eq("role[monkey]") + expect(@role.run_list_for("QA")[1]).to eq("role[duck]") + expect(@role.run_list[0]).to eq("role[acorns]") + expect(@role.run_list[1]).to be_nil + end + end + + describe "with more than one role or recipe as different arguments and list separated by comas" do + it "should add to the run list all the entries" do + @knife.name_args = [ "will", "QA", "role[monkey]", "role[duck],recipe[bird::fly]" ] + @role.run_list_for("_default") << "role[acorns]" + @knife.run + expect(@role.run_list_for("QA")[0]).to eq("role[monkey]") + expect(@role.run_list_for("QA")[1]).to eq("role[duck]") + expect(@role.run_list_for("QA")[2]).to eq("recipe[bird::fly]") + expect(@role.run_list[0]).to eq("role[acorns]") + expect(@role.run_list[1]).to be_nil + end + end + + describe "Recipe with version number is allowed" do + it "should add to the run list all the entries including the versioned recipe" do + @knife.name_args = [ "will", "QA", "role[monkey]", "role[duck],recipe[bird::fly@1.1.3]" ] + @role.run_list_for("_default") << "role[acorns]" + @knife.run + expect(@role.run_list_for("QA")[0]).to eq("role[monkey]") + expect(@role.run_list_for("QA")[1]).to eq("role[duck]") + expect(@role.run_list_for("QA")[2]).to eq("recipe[bird::fly@1.1.3]") + expect(@role.run_list[0]).to eq("role[acorns]") + expect(@role.run_list[1]).to be_nil + end + end + + describe "with one role or recipe but with an extraneous comma" do + it "should add to the run list one item" do + @role.run_list_for("_default") << "role[acorns]" + @knife.name_args = [ "will", "QA", "role[monkey]," ] + @knife.run + expect(@role.run_list_for("QA")[0]).to eq("role[monkey]") + expect(@role.run_list_for("QA")[1]).to be_nil + expect(@role.run_list[0]).to eq("role[acorns]") + expect(@role.run_list[1]).to be_nil + end + end + + describe "with more than one command" do + it "should be able to the environment run list by running multiple knife commands" do + @knife.name_args = [ "will", "QA", "role[blue]," ] + @knife.run + @knife.name_args = [ "will", "QA", "role[black]," ] + @knife.run + expect(@role.run_list_for("QA")[0]).to eq("role[blue]") + expect(@role.run_list_for("QA")[1]).to eq("role[black]") + expect(@role.run_list[0]).to be_nil + end + end + + describe "with more than one environment" do + it "should add to the run list a second environment in the specific run list" do + @role.run_list_for("_default") << "role[acorns]" + @knife.name_args = [ "will", "QA", "role[blue]," ] + @knife.run + @role.run_list_for("QA") << "role[walnuts]" + + @knife.name_args = [ "will", "PRD", "role[ball]," ] + @knife.run + @role.run_list_for("PRD") << "role[pen]" + + expect(@role.run_list_for("QA")[0]).to eq("role[blue]") + expect(@role.run_list_for("PRD")[0]).to eq("role[ball]") + expect(@role.run_list_for("QA")[1]).to eq("role[walnuts]") + expect(@role.run_list_for("PRD")[1]).to eq("role[pen]") + expect(@role.run_list[0]).to eq("role[acorns]") + expect(@role.run_list[1]).to be_nil + end + end + + end +end diff --git a/knife/spec/unit/knife/role_env_run_list_clear_spec.rb b/knife/spec/unit/knife/role_env_run_list_clear_spec.rb new file mode 100644 index 0000000000..ad88d1ae37 --- /dev/null +++ b/knife/spec/unit/knife/role_env_run_list_clear_spec.rb @@ -0,0 +1,94 @@ +# +# Author:: Adam Jacob (<adam@chef.io>) +# Author:: Will Albenzi (<walbenzi@gmail.com>) +# 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" + +describe Chef::Knife::RoleEnvRunListClear do + before(:each) do + Chef::Config[:role_name] = "will" + Chef::Config[:env_name] = "QA" + @setup = Chef::Knife::RoleEnvRunListAdd.new + @setup.name_args = [ "will", "QA", "role[monkey]", "role[person]" ] + + @knife = Chef::Knife::RoleEnvRunListClear.new + @knife.config = { + print_after: nil, + } + @knife.name_args = %w{will QA} + allow(@knife).to receive(:output).and_return(true) + + @role = Chef::Role.new + @role.name("will") + allow(@role).to receive(:save).and_return(true) + + allow(@knife.ui).to receive(:confirm).and_return(true) + allow(Chef::Role).to receive(:load).and_return(@role) + + end + + describe "run" do + + # it "should display all the things" do + # @knife.run + # @role.to_json.should == 'show all the things' + # end + + it "should load the node" do + expect(Chef::Role).to receive(:load).with("will").and_return(@role) + @knife.run + end + + it "should remove the item from the run list" do + @setup.run + @knife.run + expect(@role.run_list_for("QA")[0]).to be_nil + expect(@role.run_list[0]).to be_nil + end + + it "should save the node" do + expect(@role).to receive(:save).and_return(true) + @knife.run + end + + it "should print the run list" do + expect(@knife).to receive(:output).and_return(true) + @knife.config[:print_after] = true + @setup.run + @knife.run + end + + describe "should clear an environmental run list of roles and recipes" do + it "should remove the items from the run list" do + @setup.name_args = [ "will", "QA", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ] + @setup.run + @setup.name_args = [ "will", "PRD", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ] + @setup.run + @knife.name_args = %w{will QA} + @knife.run + expect(@role.run_list_for("QA")[0]).to be_nil + expect(@role.run_list_for("PRD")[0]).to eq("recipe[orange::chicken]") + expect(@role.run_list_for("PRD")[1]).to eq("role[monkey]") + expect(@role.run_list_for("PRD")[2]).to eq("recipe[duck::type]") + expect(@role.run_list_for("PRD")[3]).to eq("role[person]") + expect(@role.run_list_for("PRD")[4]).to eq("role[bird]") + expect(@role.run_list_for("PRD")[5]).to eq("role[town]") + end + end + end +end diff --git a/knife/spec/unit/knife/role_env_run_list_remove_spec.rb b/knife/spec/unit/knife/role_env_run_list_remove_spec.rb new file mode 100644 index 0000000000..8755ce452b --- /dev/null +++ b/knife/spec/unit/knife/role_env_run_list_remove_spec.rb @@ -0,0 +1,102 @@ +# +# Author:: Adam Jacob (<adam@chef.io>) +# Author:: Will Albenzi (<walbenzi@gmail.com>) +# 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" + +describe Chef::Knife::RoleEnvRunListRemove do + before(:each) do + Chef::Config[:role_name] = "will" + Chef::Config[:env_name] = "QA" + @setup = Chef::Knife::RoleEnvRunListAdd.new + @setup.name_args = [ "will", "QA", "role[monkey]", "role[person]" ] + + @knife = Chef::Knife::RoleEnvRunListRemove.new + @knife.config = { + print_after: nil, + } + @knife.name_args = [ "will", "QA", "role[monkey]" ] + allow(@knife).to receive(:output).and_return(true) + + @role = Chef::Role.new + @role.name("will") + allow(@role).to receive(:save).and_return(true) + + allow(@knife.ui).to receive(:confirm).and_return(true) + allow(Chef::Role).to receive(:load).and_return(@role) + + end + + describe "run" do + + # it "should display all the things" do + # @knife.run + # @role.to_json.should == 'show all the things' + # end + + it "should load the node" do + expect(Chef::Role).to receive(:load).with("will").and_return(@role) + @knife.run + end + + it "should remove the item from the run list" do + @setup.run + @knife.run + expect(@role.run_list_for("QA")[0]).not_to eq("role[monkey]") + expect(@role.run_list_for("QA")[0]).to eq("role[person]") + expect(@role.run_list[0]).to be_nil + end + + it "should save the node" do + expect(@role).to receive(:save).and_return(true) + @knife.run + end + + it "should print the run list" do + expect(@knife).to receive(:output).and_return(true) + @knife.config[:print_after] = true + @setup.run + @knife.run + end + + describe "run with a list of roles and recipes" do + it "should remove the items from the run list" do + @setup.name_args = [ "will", "QA", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ] + @setup.run + @setup.name_args = [ "will", "PRD", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ] + @setup.run + @knife.name_args = [ "will", "QA", "role[monkey]" ] + @knife.run + @knife.name_args = [ "will", "QA", "recipe[duck::type]" ] + @knife.run + expect(@role.run_list_for("QA")).not_to include("role[monkey]") + expect(@role.run_list_for("QA")).not_to include("recipe[duck::type]") + expect(@role.run_list_for("QA")[0]).to eq("recipe[orange::chicken]") + expect(@role.run_list_for("QA")[1]).to eq("role[person]") + expect(@role.run_list_for("QA")[2]).to eq("role[bird]") + expect(@role.run_list_for("QA")[3]).to eq("role[town]") + expect(@role.run_list_for("PRD")[0]).to eq("recipe[orange::chicken]") + expect(@role.run_list_for("PRD")[1]).to eq("role[monkey]") + expect(@role.run_list_for("PRD")[2]).to eq("recipe[duck::type]") + expect(@role.run_list_for("PRD")[3]).to eq("role[person]") + expect(@role.run_list_for("PRD")[4]).to eq("role[bird]") + expect(@role.run_list_for("PRD")[5]).to eq("role[town]") + end + end + end +end diff --git a/knife/spec/unit/knife/role_env_run_list_replace_spec.rb b/knife/spec/unit/knife/role_env_run_list_replace_spec.rb new file mode 100644 index 0000000000..457f4efbd7 --- /dev/null +++ b/knife/spec/unit/knife/role_env_run_list_replace_spec.rb @@ -0,0 +1,105 @@ +# +# Author:: Adam Jacob (<adam@chef.io>) +# Author:: Will Albenzi (<walbenzi@gmail.com>) +# 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" + +describe Chef::Knife::RoleEnvRunListReplace do + before(:each) do + Chef::Config[:role_name] = "will" + Chef::Config[:env_name] = "QA" + @setup = Chef::Knife::RoleEnvRunListAdd.new + @setup.name_args = [ "will", "QA", "role[monkey]", "role[dude]", "role[fixer]" ] + + @knife = Chef::Knife::RoleEnvRunListReplace.new + @knife.config = { + print_after: nil, + } + @knife.name_args = [ "will", "QA", "role[dude]", "role[person]" ] + allow(@knife).to receive(:output).and_return(true) + + @role = Chef::Role.new + @role.name("will") + allow(@role).to receive(:save).and_return(true) + + allow(@knife.ui).to receive(:confirm).and_return(true) + allow(Chef::Role).to receive(:load).and_return(@role) + + end + + describe "run" do + + # it "should display all the things" do + # @knife.run + # @role.to_json.should == 'show all the things' + # end + + it "should load the node" do + expect(Chef::Role).to receive(:load).with("will").and_return(@role) + @knife.run + end + + it "should remove the item from the run list" do + @setup.run + @knife.run + expect(@role.run_list_for("QA")[1]).not_to eq("role[dude]") + expect(@role.run_list_for("QA")[1]).to eq("role[person]") + expect(@role.run_list[0]).to be_nil + end + + it "should save the node" do + expect(@role).to receive(:save).and_return(true) + @knife.run + end + + it "should print the run list" do + expect(@knife).to receive(:output).and_return(true) + @knife.config[:print_after] = true + @setup.run + @knife.run + end + + describe "run with a list of roles and recipes" do + it "should replace the items from the run list" do + @setup.name_args = [ "will", "QA", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ] + @setup.run + @setup.name_args = [ "will", "PRD", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ] + @setup.run + @knife.name_args = [ "will", "QA", "role[monkey]", "role[gibbon]" ] + @knife.run + @knife.name_args = [ "will", "QA", "recipe[duck::type]", "recipe[duck::mallard]" ] + @knife.run + expect(@role.run_list_for("QA")).not_to include("role[monkey]") + expect(@role.run_list_for("QA")).not_to include("recipe[duck::type]") + expect(@role.run_list_for("QA")[0]).to eq("recipe[orange::chicken]") + expect(@role.run_list_for("QA")[1]).to eq("role[gibbon]") + expect(@role.run_list_for("QA")[2]).to eq("recipe[duck::mallard]") + expect(@role.run_list_for("QA")[3]).to eq("role[person]") + expect(@role.run_list_for("QA")[4]).to eq("role[bird]") + expect(@role.run_list_for("QA")[5]).to eq("role[town]") + expect(@role.run_list_for("PRD")[0]).to eq("recipe[orange::chicken]") + expect(@role.run_list_for("PRD")[1]).to eq("role[monkey]") + expect(@role.run_list_for("PRD")[2]).to eq("recipe[duck::type]") + expect(@role.run_list_for("PRD")[3]).to eq("role[person]") + expect(@role.run_list_for("PRD")[4]).to eq("role[bird]") + expect(@role.run_list_for("PRD")[5]).to eq("role[town]") + expect(@role.run_list[0]).to be_nil + end + end + end +end diff --git a/knife/spec/unit/knife/role_env_run_list_set_spec.rb b/knife/spec/unit/knife/role_env_run_list_set_spec.rb new file mode 100644 index 0000000000..34233398f5 --- /dev/null +++ b/knife/spec/unit/knife/role_env_run_list_set_spec.rb @@ -0,0 +1,99 @@ +# +# Author:: Adam Jacob (<adam@chef.io>) +# Author:: Will Albenzi (<walbenzi@gmail.com>) +# 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" + +describe Chef::Knife::RoleEnvRunListSet do + before(:each) do + Chef::Config[:role_name] = "will" + Chef::Config[:env_name] = "QA" + @setup = Chef::Knife::RoleEnvRunListAdd.new + @setup.name_args = [ "will", "QA", "role[monkey]", "role[person]", "role[bucket]" ] + + @knife = Chef::Knife::RoleEnvRunListSet.new + @knife.config = { + print_after: nil, + } + @knife.name_args = [ "will", "QA", "role[owen]", "role[mauntel]" ] + allow(@knife).to receive(:output).and_return(true) + + @role = Chef::Role.new + @role.name("will") + allow(@role).to receive(:save).and_return(true) + + allow(@knife.ui).to receive(:confirm).and_return(true) + allow(Chef::Role).to receive(:load).and_return(@role) + + end + + describe "run" do + + # it "should display all the things" do + # @knife.run + # @role.to_json.should == 'show all the things' + # end + + it "should load the node" do + expect(Chef::Role).to receive(:load).with("will").and_return(@role) + @knife.run + end + + it "should replace all the items in the runlist with what is specified" do + @setup.run + @knife.run + expect(@role.run_list_for("QA")[0]).to eq("role[owen]") + expect(@role.run_list_for("QA")[1]).to eq("role[mauntel]") + expect(@role.run_list_for("QA")[2]).to be_nil + expect(@role.run_list[0]).to be_nil + end + + it "should save the node" do + expect(@role).to receive(:save).and_return(true) + @knife.run + end + + it "should print the run list" do + expect(@knife).to receive(:output).and_return(true) + @knife.config[:print_after] = true + @setup.run + @knife.run + end + + describe "should clear an environmental run list of roles and recipes" do + it "should remove the items from the run list" do + @setup.name_args = [ "will", "QA", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ] + @setup.run + @setup.name_args = [ "will", "PRD", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ] + @setup.run + @knife.name_args = [ "will", "QA", "role[coke]", "role[pepsi]" ] + @knife.run + expect(@role.run_list_for("QA")[0]).to eq("role[coke]") + expect(@role.run_list_for("QA")[1]).to eq("role[pepsi]") + expect(@role.run_list_for("QA")[2]).to be_nil + expect(@role.run_list_for("PRD")[0]).to eq("recipe[orange::chicken]") + expect(@role.run_list_for("PRD")[1]).to eq("role[monkey]") + expect(@role.run_list_for("PRD")[2]).to eq("recipe[duck::type]") + expect(@role.run_list_for("PRD")[3]).to eq("role[person]") + expect(@role.run_list_for("PRD")[4]).to eq("role[bird]") + expect(@role.run_list_for("PRD")[5]).to eq("role[town]") + expect(@role.run_list[0]).to be_nil + end + end + end +end diff --git a/knife/spec/unit/knife/role_from_file_spec.rb b/knife/spec/unit/knife/role_from_file_spec.rb new file mode 100644 index 0000000000..6e2fdf7cfb --- /dev/null +++ b/knife/spec/unit/knife/role_from_file_spec.rb @@ -0,0 +1,69 @@ +# +# Author:: Adam Jacob (<adam@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" + +Chef::Knife::RoleFromFile.load_deps + +describe Chef::Knife::RoleFromFile do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::RoleFromFile.new + @knife.config = { + print_after: nil, + } + @knife.name_args = [ "adam.rb" ] + allow(@knife).to receive(:output).and_return(true) + allow(@knife).to receive(:confirm).and_return(true) + @role = Chef::Role.new + allow(@role).to receive(:save) + allow(@knife.loader).to receive(:load_from).and_return(@role) + @stdout = StringIO.new + allow(@knife.ui).to receive(:stdout).and_return(@stdout) + end + + describe "run" do + it "should load from a file" do + expect(@knife.loader).to receive(:load_from).with("roles", "adam.rb").and_return(@role) + @knife.run + end + + it "should not print the role" do + expect(@knife).not_to receive(:output) + @knife.run + end + + describe "with -p or --print-after" do + it "should print the role" do + @knife.config[:print_after] = true + expect(@knife).to receive(:output) + @knife.run + end + end + end + + describe "run with multiple arguments" do + it "should load each file" do + @knife.name_args = [ "adam.rb", "caleb.rb" ] + expect(@knife.loader).to receive(:load_from).with("roles", "adam.rb").and_return(@role) + expect(@knife.loader).to receive(:load_from).with("roles", "caleb.rb").and_return(@role) + @knife.run + end + end + +end diff --git a/knife/spec/unit/knife/role_list_spec.rb b/knife/spec/unit/knife/role_list_spec.rb new file mode 100644 index 0000000000..f37a85b6dc --- /dev/null +++ b/knife/spec/unit/knife/role_list_spec.rb @@ -0,0 +1,54 @@ +# +# Author:: Adam Jacob (<adam@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" + +describe Chef::Knife::RoleList do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::RoleList.new + allow(@knife).to receive(:output).and_return(true) + @list = { + "foo" => "http://example.com/foo", + "bar" => "http://example.com/foo", + } + allow(Chef::Role).to receive(:list).and_return(@list) + end + + describe "run" do + it "should list the roles" do + expect(Chef::Role).to receive(:list).and_return(@list) + @knife.run + end + + it "should pretty print the list" do + expect(Chef::Role).to receive(:list).and_return(@list) + expect(@knife).to receive(:output).with(%w{bar foo}) + @knife.run + end + + describe "with -w or --with-uri" do + it "should pretty print the hash" do + @knife.config[:with_uri] = true + expect(Chef::Role).to receive(:list).and_return(@list) + expect(@knife).to receive(:output).with(@list) + @knife.run + end + end + end +end diff --git a/knife/spec/unit/knife/role_run_list_add_spec.rb b/knife/spec/unit/knife/role_run_list_add_spec.rb new file mode 100644 index 0000000000..7b038c2e81 --- /dev/null +++ b/knife/spec/unit/knife/role_run_list_add_spec.rb @@ -0,0 +1,179 @@ +# +# Author:: Adam Jacob (<adam@chef.io>) +# Author:: Will Albenzi (<walbenzi@gmail.com>) +# 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" + +describe Chef::Knife::RoleRunListAdd do + before(:each) do + # Chef::Config[:role_name] = "websimian" + # Chef::Config[:env_name] = "QA" + @knife = Chef::Knife::RoleRunListAdd.new + @knife.config = { + after: nil, + } + @knife.name_args = [ "will", "role[monkey]" ] + allow(@knife).to receive(:output).and_return(true) + @role = Chef::Role.new + allow(@role).to receive(:save).and_return(true) + allow(Chef::Role).to receive(:load).and_return(@role) + end + + describe "run" do + + # it "should display all the things" do + # @knife.run + # @role.to_json.should == 'show all the things' + # end + + it "should have a run list with the monkey role" do + @knife.run + expect(@role.run_list[0]).to eq("role[monkey]") + end + + it "should load the role named will" do + expect(Chef::Role).to receive(:load).with("will") + @knife.run + end + + it "should save the role" do + expect(@role).to receive(:save) + @knife.run + end + + it "should print the run list" do + expect(@knife).to receive(:output).and_return(true) + @knife.run + end + + describe "with -a or --after specified" do + it "should not create a change if the specified 'after' never comes" do + @role.run_list_for("_default") << "role[acorns]" + @role.run_list_for("_default") << "role[barn]" + @knife.config[:after] = "role[tree]" + @knife.name_args = [ "will", "role[pad]" ] + @knife.run + expect(@role.run_list[0]).to eq("role[acorns]") + expect(@role.run_list[1]).to eq("role[barn]") + expect(@role.run_list[2]).to be_nil + end + + it "should add to the run list after the specified entries in the default run list" do + # Setup + @role.run_list_for("_default") << "role[acorns]" + @role.run_list_for("_default") << "role[barn]" + # Configuration we are testing + @knife.config[:after] = "role[acorns]" + @knife.name_args = [ "will", "role[pad]", "role[whackadoo]" ] + @knife.run + # The actual tests + expect(@role.run_list[0]).to eq("role[acorns]") + expect(@role.run_list[1]).to eq("role[pad]") + expect(@role.run_list[2]).to eq("role[whackadoo]") + expect(@role.run_list[3]).to eq("role[barn]") + expect(@role.run_list[4]).to be_nil + end + end + + describe "with more than one role or recipe" do + it "should add to the QA run list all the entries" do + @knife.name_args = [ "will", "role[monkey],role[duck]" ] + @role.run_list_for("_default") << "role[acorns]" + @knife.run + expect(@role.run_list[0]).to eq("role[acorns]") + expect(@role.run_list[1]).to eq("role[monkey]") + expect(@role.run_list[2]).to eq("role[duck]") + expect(@role.run_list[3]).to be_nil + end + end + + describe "with more than one role or recipe with space between items" do + it "should add to the run list all the entries" do + @knife.name_args = [ "will", "role[monkey], role[duck]" ] + @role.run_list_for("_default") << "role[acorns]" + @knife.run + expect(@role.run_list[0]).to eq("role[acorns]") + expect(@role.run_list[1]).to eq("role[monkey]") + expect(@role.run_list[2]).to eq("role[duck]") + expect(@role.run_list[3]).to be_nil + end + end + + describe "with more than one role or recipe as different arguments" do + it "should add to the run list all the entries" do + @knife.name_args = [ "will", "role[monkey]", "role[duck]" ] + @role.run_list_for("_default") << "role[acorns]" + @knife.run + expect(@role.run_list[0]).to eq("role[acorns]") + expect(@role.run_list[1]).to eq("role[monkey]") + expect(@role.run_list[2]).to eq("role[duck]") + expect(@role.run_list[3]).to be_nil + end + end + + describe "with more than one role or recipe as different arguments and list separated by comas" do + it "should add to the run list all the entries" do + @knife.name_args = [ "will", "role[monkey]", "role[duck],recipe[bird::fly]" ] + @role.run_list_for("_default") << "role[acorns]" + @knife.run + expect(@role.run_list[0]).to eq("role[acorns]") + expect(@role.run_list[1]).to eq("role[monkey]") + expect(@role.run_list[2]).to eq("role[duck]") + expect(@role.run_list[3]).to eq("recipe[bird::fly]") + expect(@role.run_list[4]).to be_nil + end + end + + describe "Recipe with version number is allowed" do + it "should add to the run list all the entries including the versioned recipe" do + @knife.name_args = [ "will", "role[monkey]", "role[duck],recipe[bird::fly@1.1.3]" ] + @role.run_list_for("_default") << "role[acorns]" + @knife.run + expect(@role.run_list[0]).to eq("role[acorns]") + expect(@role.run_list[1]).to eq("role[monkey]") + expect(@role.run_list[2]).to eq("role[duck]") + expect(@role.run_list[3]).to eq("recipe[bird::fly@1.1.3]") + expect(@role.run_list[4]).to be_nil + end + end + + describe "with one role or recipe but with an extraneous comma" do + it "should add to the run list one item" do + @role.run_list_for("_default") << "role[acorns]" + @knife.name_args = [ "will", "role[monkey]," ] + @knife.run + expect(@role.run_list[0]).to eq("role[acorns]") + expect(@role.run_list[1]).to eq("role[monkey]") + expect(@role.run_list[2]).to be_nil + end + end + + describe "with more than one command" do + it "should be able to the environment run list by running multiple knife commands" do + @knife.name_args = [ "will", "role[blue]," ] + @knife.run + @knife.name_args = [ "will", "role[black]," ] + @knife.run + expect(@role.run_list[0]).to eq("role[blue]") + expect(@role.run_list[1]).to eq("role[black]") + expect(@role.run_list[2]).to be_nil + end + end + + end +end diff --git a/knife/spec/unit/knife/role_run_list_clear_spec.rb b/knife/spec/unit/knife/role_run_list_clear_spec.rb new file mode 100644 index 0000000000..5479b01811 --- /dev/null +++ b/knife/spec/unit/knife/role_run_list_clear_spec.rb @@ -0,0 +1,84 @@ +# +# Author:: Adam Jacob (<adam@chef.io>) +# Author:: Will Albenzi (<walbenzi@gmail.com>) +# 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" + +describe Chef::Knife::RoleRunListClear do + before(:each) do + Chef::Config[:role_name] = "will" + @setup = Chef::Knife::RoleRunListAdd.new + @setup.name_args = [ "will", "role[monkey]", "role[person]" ] + + @knife = Chef::Knife::RoleRunListClear.new + @knife.config = { + print_after: nil, + } + @knife.name_args = [ "will" ] + allow(@knife).to receive(:output).and_return(true) + + @role = Chef::Role.new + @role.name("will") + allow(@role).to receive(:save).and_return(true) + + allow(@knife.ui).to receive(:confirm).and_return(true) + allow(Chef::Role).to receive(:load).and_return(@role) + + end + + describe "run" do + + # it "should display all the things" do + # @knife.run + # @role.to_json.should == 'show all the things' + # end + + it "should load the node" do + expect(Chef::Role).to receive(:load).with("will").and_return(@role) + @knife.run + end + + it "should remove the item from the run list" do + @setup.run + @knife.run + expect(@role.run_list[0]).to be_nil + end + + it "should save the node" do + expect(@role).to receive(:save).and_return(true) + @knife.run + end + + it "should print the run list" do + expect(@knife).to receive(:output).and_return(true) + @knife.config[:print_after] = true + @setup.run + @knife.run + end + + describe "should clear an environmental run list of roles and recipes" do + it "should remove the items from the run list" do + @setup.name_args = [ "will", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ] + @setup.run + @knife.name_args = [ "will" ] + @knife.run + expect(@role.run_list[0]).to be_nil + end + end + end +end diff --git a/knife/spec/unit/knife/role_run_list_remove_spec.rb b/knife/spec/unit/knife/role_run_list_remove_spec.rb new file mode 100644 index 0000000000..353ae36c1a --- /dev/null +++ b/knife/spec/unit/knife/role_run_list_remove_spec.rb @@ -0,0 +1,92 @@ +# +# Author:: Adam Jacob (<adam@chef.io>) +# Author:: Will Albenzi (<walbenzi@gmail.com>) +# 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" + +describe Chef::Knife::RoleRunListRemove do + before(:each) do + Chef::Config[:role_name] = "will" + @setup = Chef::Knife::RoleRunListAdd.new + @setup.name_args = [ "will", "role[monkey]", "role[person]" ] + + @knife = Chef::Knife::RoleRunListRemove.new + @knife.config = { + print_after: nil, + } + @knife.name_args = [ "will", "role[monkey]" ] + allow(@knife).to receive(:output).and_return(true) + + @role = Chef::Role.new + @role.name("will") + allow(@role).to receive(:save).and_return(true) + + allow(@knife.ui).to receive(:confirm).and_return(true) + allow(Chef::Role).to receive(:load).and_return(@role) + + end + + describe "run" do + + # it "should display all the things" do + # @knife.run + # @role.to_json.should == 'show all the things' + # end + + it "should load the node" do + expect(Chef::Role).to receive(:load).with("will").and_return(@role) + @knife.run + end + + it "should remove the item from the run list" do + @setup.run + @knife.run + expect(@role.run_list[0]).to eq("role[person]") + expect(@role.run_list[1]).to be_nil + end + + it "should save the node" do + expect(@role).to receive(:save).and_return(true) + @knife.run + end + + it "should print the run list" do + expect(@knife).to receive(:output).and_return(true) + @knife.config[:print_after] = true + @setup.run + @knife.run + end + + describe "run with a list of roles and recipes" do + it "should remove the items from the run list" do + @setup.name_args = [ "will", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ] + @setup.run + @knife.name_args = [ "will", "role[monkey]" ] + @knife.run + @knife.name_args = [ "will", "recipe[duck::type]" ] + @knife.run + expect(@role.run_list).not_to include("role[monkey]") + expect(@role.run_list).not_to include("recipe[duck::type]") + expect(@role.run_list[0]).to eq("recipe[orange::chicken]") + expect(@role.run_list[1]).to eq("role[person]") + expect(@role.run_list[2]).to eq("role[bird]") + expect(@role.run_list[3]).to eq("role[town]") + end + end + end +end diff --git a/knife/spec/unit/knife/role_run_list_replace_spec.rb b/knife/spec/unit/knife/role_run_list_replace_spec.rb new file mode 100644 index 0000000000..e59b704f00 --- /dev/null +++ b/knife/spec/unit/knife/role_run_list_replace_spec.rb @@ -0,0 +1,98 @@ +# +# Author:: Adam Jacob (<adam@chef.io>) +# Author:: Will Albenzi (<walbenzi@gmail.com>) +# 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" + +describe Chef::Knife::RoleRunListReplace do + before(:each) do + Chef::Config[:role_name] = "will" + @setup = Chef::Knife::RoleRunListAdd.new + @setup.name_args = [ "will", "role[monkey]", "role[dude]", "role[fixer]" ] + + @knife = Chef::Knife::RoleRunListReplace.new + @knife.config = { + print_after: nil, + } + @knife.name_args = [ "will", "role[dude]", "role[person]" ] + allow(@knife).to receive(:output).and_return(true) + + @role = Chef::Role.new + @role.name("will") + allow(@role).to receive(:save).and_return(true) + + allow(@knife.ui).to receive(:confirm).and_return(true) + allow(Chef::Role).to receive(:load).and_return(@role) + + end + + describe "run" do + + # it "should display all the things" do + # @knife.run + # @role.to_json.should == 'show all the things' + # end + + it "should load the node" do + expect(Chef::Role).to receive(:load).with("will").and_return(@role) + @knife.run + end + + it "should remove the item from the run list" do + @setup.run + @knife.run + expect(@role.run_list[0]).to eq("role[monkey]") + expect(@role.run_list[1]).not_to eq("role[dude]") + expect(@role.run_list[1]).to eq("role[person]") + expect(@role.run_list[2]).to eq("role[fixer]") + expect(@role.run_list[3]).to be_nil + end + + it "should save the node" do + expect(@role).to receive(:save).and_return(true) + @knife.run + end + + it "should print the run list" do + expect(@knife).to receive(:output).and_return(true) + @knife.config[:print_after] = true + @setup.run + @knife.run + end + + describe "run with a list of roles and recipes" do + it "should replace the items from the run list" do + @setup.name_args = [ "will", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ] + @setup.run + @knife.name_args = [ "will", "role[monkey]", "role[gibbon]" ] + @knife.run + @knife.name_args = [ "will", "recipe[duck::type]", "recipe[duck::mallard]" ] + @knife.run + expect(@role.run_list).not_to include("role[monkey]") + expect(@role.run_list).not_to include("recipe[duck::type]") + expect(@role.run_list[0]).to eq("recipe[orange::chicken]") + expect(@role.run_list[1]).to eq("role[gibbon]") + expect(@role.run_list[2]).to eq("recipe[duck::mallard]") + expect(@role.run_list[3]).to eq("role[person]") + expect(@role.run_list[4]).to eq("role[bird]") + expect(@role.run_list[5]).to eq("role[town]") + expect(@role.run_list[6]).to be_nil + end + end + end +end diff --git a/knife/spec/unit/knife/role_run_list_set_spec.rb b/knife/spec/unit/knife/role_run_list_set_spec.rb new file mode 100644 index 0000000000..b75f1ab377 --- /dev/null +++ b/knife/spec/unit/knife/role_run_list_set_spec.rb @@ -0,0 +1,89 @@ +# +# Author:: Adam Jacob (<adam@chef.io>) +# Author:: Will Albenzi (<walbenzi@gmail.com>) +# 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" + +describe Chef::Knife::RoleRunListSet do + before(:each) do + Chef::Config[:role_name] = "will" + @setup = Chef::Knife::RoleRunListAdd.new + @setup.name_args = [ "will", "role[monkey]", "role[person]", "role[bucket]" ] + + @knife = Chef::Knife::RoleRunListSet.new + @knife.config = { + print_after: nil, + } + @knife.name_args = [ "will", "role[owen]", "role[mauntel]" ] + allow(@knife).to receive(:output).and_return(true) + + @role = Chef::Role.new + @role.name("will") + allow(@role).to receive(:save).and_return(true) + + allow(@knife.ui).to receive(:confirm).and_return(true) + allow(Chef::Role).to receive(:load).and_return(@role) + + end + + describe "run" do + + # it "should display all the things" do + # @knife.run + # @role.to_json.should == 'show all the things' + # end + + it "should load the node" do + expect(Chef::Role).to receive(:load).with("will").and_return(@role) + @knife.run + end + + it "should replace all the items in the runlist with what is specified" do + @setup.run + @knife.run + expect(@role.run_list[0]).to eq("role[owen]") + expect(@role.run_list[1]).to eq("role[mauntel]") + expect(@role.run_list[2]).to be_nil + end + + it "should save the node" do + expect(@role).to receive(:save).and_return(true) + @knife.run + end + + it "should print the run list" do + expect(@knife).to receive(:output).and_return(true) + @knife.config[:print_after] = true + @setup.run + @knife.run + end + + describe "should clear an environmental run list of roles and recipes" do + it "should remove the items from the run list" do + @setup.name_args = [ "will", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ] + @setup.run + @knife.name_args = [ "will", "role[coke]", "role[pepsi]" ] + @knife.run + expect(@role.run_list[0]).to eq("role[coke]") + expect(@role.run_list[1]).to eq("role[pepsi]") + expect(@role.run_list[2]).to be_nil + expect(@role.run_list[3]).to be_nil + end + end + end +end diff --git a/knife/spec/unit/knife/role_show_spec.rb b/knife/spec/unit/knife/role_show_spec.rb new file mode 100644 index 0000000000..a79cb40e81 --- /dev/null +++ b/knife/spec/unit/knife/role_show_spec.rb @@ -0,0 +1,59 @@ +# +# Author:: Lamont Granquist (<lamont@chef.io>) +# Copyright:: Copyright 2014-2016, Lamont Granquist +# 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" + +describe Chef::Knife::RoleShow do + let(:role) { "base" } + + let(:knife) do + knife = Chef::Knife::RoleShow.new + knife.name_args = [ role ] + knife + end + + let(:role_mock) { double("role_mock") } + + describe "run" do + it "should list the role" do + expect(Chef::Role).to receive(:load).with("base").and_return(role_mock) + expect(knife).to receive(:format_for_display).with(role_mock) + knife.run + end + + it "should pretty print json" do + knife.config[:format] = "json" + stdout = StringIO.new + allow(knife.ui).to receive(:stdout).and_return(stdout) + fake_role_contents = { "foo" => "bar", "baz" => "qux" } + expect(Chef::Role).to receive(:load).with("base").and_return(fake_role_contents) + knife.run + expect(stdout.string).to eql("{\n \"foo\": \"bar\",\n \"baz\": \"qux\"\n}\n") + end + + context "without a role name" do + let(:role) {} + + it "should print usage and exit when a role name is not provided" do + expect(knife).to receive(:show_usage) + expect(knife.ui).to receive(:fatal) + expect { knife.run }.to raise_error(SystemExit) + end + end + end +end diff --git a/knife/spec/unit/knife/ssh_spec.rb b/knife/spec/unit/knife/ssh_spec.rb new file mode 100644 index 0000000000..59015f024a --- /dev/null +++ b/knife/spec/unit/knife/ssh_spec.rb @@ -0,0 +1,403 @@ +# +# Author:: Bryan McLellan <btm@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 "net/ssh" +require "net/ssh/multi" + +describe Chef::Knife::Ssh do + let(:query_result) { double("chef search results") } + + before do + Chef::Config[:client_key] = CHEF_SPEC_DATA + "/ssl/private_key.pem" + @knife = Chef::Knife::Ssh.new + @knife.merge_configs + @node_foo = {} + @node_foo["fqdn"] = "foo.example.org" + @node_foo["ipaddress"] = "10.0.0.1" + @node_foo["cloud"] = {} + + @node_bar = {} + @node_bar["fqdn"] = "bar.example.org" + @node_bar["ipaddress"] = "10.0.0.2" + @node_bar["cloud"] = {} + + end + + describe "#configure_session" do + context "manual is set to false (default)" do + before do + @knife.config[:manual] = false + allow(query_result).to receive(:search).with(any_args).and_yield(@node_foo).and_yield(@node_bar) + allow(Chef::Search::Query).to receive(:new).and_return(query_result) + end + + def self.should_return_specified_attributes + it "returns an array of the attributes specified on the command line OR config file, if only one is set" do + @node_bar["target"] = "10.0.0.2" + @node_foo["target"] = "10.0.0.1" + @node_bar["prefix"] = "bar" + @node_foo["prefix"] = "foo" + @knife.config[:ssh_attribute] = "ipaddress" + @knife.config[:prefix_attribute] = "name" + Chef::Config[:knife][:ssh_attribute] = "ipaddress" # this value will be in the config file + Chef::Config[:knife][:prefix_attribute] = "name" # this value will be in the config file + expect(@knife).to receive(:session_from_list).with([["10.0.0.1", nil, "foo"], ["10.0.0.2", nil, "bar"]]) + @knife.configure_session + end + + it "returns an array of the attributes specified on the command line even when a config value is set" do + @node_bar["target"] = "10.0.0.2" + @node_foo["target"] = "10.0.0.1" + @node_bar["prefix"] = "bar" + @node_foo["prefix"] = "foo" + Chef::Config[:knife][:ssh_attribute] = "config_file" # this value will be in the config file + Chef::Config[:knife][:prefix_attribute] = "config_file" # this value will be in the config file + @knife.config[:ssh_attribute] = "ipaddress" # this is the value of the command line via #configure_attribute + @knife.config[:prefix_attribute] = "name" # this is the value of the command line via #configure_attribute + expect(@knife).to receive(:session_from_list).with([["10.0.0.1", nil, "foo"], ["10.0.0.2", nil, "bar"]]) + @knife.configure_session + end + end + + it "searches for and returns an array of fqdns" do + expect(@knife).to receive(:session_from_list).with([ + ["foo.example.org", nil, nil], + ["bar.example.org", nil, nil], + ]) + @knife.configure_session + end + + should_return_specified_attributes + + context "when cloud hostnames are available" do + before do + @node_foo["cloud"]["public_hostname"] = "ec2-10-0-0-1.compute-1.amazonaws.com" + @node_bar["cloud"]["public_hostname"] = "ec2-10-0-0-2.compute-1.amazonaws.com" + end + it "returns an array of cloud public hostnames" do + expect(@knife).to receive(:session_from_list).with([ + ["ec2-10-0-0-1.compute-1.amazonaws.com", nil, nil], + ["ec2-10-0-0-2.compute-1.amazonaws.com", nil, nil], + ]) + @knife.configure_session + end + + should_return_specified_attributes + end + + context "when cloud hostnames are available but empty" do + before do + @node_foo["cloud"]["public_hostname"] = "" + @node_bar["cloud"]["public_hostname"] = "" + end + + it "returns an array of fqdns" do + expect(@knife).to receive(:session_from_list).with([ + ["foo.example.org", nil, nil], + ["bar.example.org", nil, nil], + ]) + @knife.configure_session + end + + should_return_specified_attributes + end + + it "should raise an error if no host are found" do + allow(query_result).to receive(:search).with(any_args) + expect(@knife.ui).to receive(:fatal) + expect(@knife).to receive(:exit).with(10) + @knife.configure_session + end + + context "when there are some hosts found but they do not have an attribute to connect with" do + before do + @node_foo["fqdn"] = nil + @node_bar["fqdn"] = nil + end + + it "should raise a specific error (CHEF-3402)" do + expect(@knife.ui).to receive(:fatal).with(/^2 nodes found/) + expect(@knife).to receive(:exit).with(10) + @knife.configure_session + end + end + + context "when there are some hosts found but IPs duplicated if duplicated_fqdns option sets :fatal" do + before do + @knife.config[:duplicated_fqdns] = :fatal + @node_foo["fqdn"] = "foo.example.org" + @node_bar["fqdn"] = "foo.example.org" + end + + it "should raise a specific error" do + expect(@knife.ui).to receive(:fatal).with(/^SSH node is duplicated: foo\.example\.org/) + expect(@knife).to receive(:exit).with(10) + expect(@knife).to receive(:session_from_list).with([ + ["foo.example.org", nil, nil], + ["foo.example.org", nil, nil], + ]) + @knife.configure_session + end + end + end + + context "manual is set to true" do + before do + @knife.config[:manual] = true + end + + it "returns an array of provided values" do + @knife.instance_variable_set(:@name_args, ["foo.example.org bar.example.org"]) + expect(@knife).to receive(:session_from_list).with(["foo.example.org", "bar.example.org"]) + @knife.configure_session + end + end + end + + describe "#get_prefix_attribute" do + # Order of precedence for prefix + # 1) config value (cli or knife config) + # 2) nil + before do + Chef::Config[:knife][:prefix_attribute] = nil + @knife.config[:prefix_attribute] = nil + @node_foo["cloud"]["public_hostname"] = "ec2-10-0-0-1.compute-1.amazonaws.com" + @node_bar["cloud"]["public_hostname"] = "" + end + + it "should return nil by default" do + expect(@knife.get_prefix_attribute({})).to eq(nil) + end + + it "should favor config over nil" do + @node_foo["prefix"] = "config" + expect( @knife.get_prefix_attribute(@node_foo)).to eq("config") + end + end + + describe "#get_ssh_attribute" do + # Order of precedence for ssh target + # 1) config value (cli or knife config) + # 2) cloud attribute + # 3) fqdn + before do + Chef::Config[:knife][:ssh_attribute] = nil + @knife.config[:ssh_attribute] = nil + @node_foo["cloud"]["public_hostname"] = "ec2-10-0-0-1.compute-1.amazonaws.com" + @node_bar["cloud"]["public_hostname"] = "" + end + + it "should return fqdn by default" do + expect(@knife.get_ssh_attribute({ "fqdn" => "fqdn" })).to eq("fqdn") + end + + it "should return cloud.public_hostname attribute if available" do + expect(@knife.get_ssh_attribute(@node_foo)).to eq("ec2-10-0-0-1.compute-1.amazonaws.com") + end + + it "should favor config over cloud and default" do + @node_foo["target"] = "config" + expect( @knife.get_ssh_attribute(@node_foo)).to eq("config") + end + + it "should return fqdn if cloud.hostname is empty" do + expect( @knife.get_ssh_attribute(@node_bar)).to eq("bar.example.org") + end + end + + describe "#session_from_list" do + before :each do + @knife.instance_variable_set(:@longest, 0) + ssh_config = { timeout: 50, user: "locutus", port: 23, keepalive: true, keepalive_interval: 60 } + allow(Net::SSH).to receive(:configuration_for).with("the.b.org", true).and_return(ssh_config) + end + + it "uses the port from an ssh config file" do + @knife.session_from_list([["the.b.org", nil, nil]]) + expect(@knife.session.servers[0].port).to eq(23) + end + + it "uses the port from a cloud attr" do + @knife.session_from_list([["the.b.org", 123, nil]]) + expect(@knife.session.servers[0].port).to eq(123) + end + + it "uses the prefix from list" do + @knife.session_from_list([["the.b.org", nil, "b-team"]]) + expect(@knife.session.servers[0][:prefix]).to eq("b-team") + end + + it "defaults to a prefix of host" do + @knife.session_from_list([["the.b.org", nil, nil]]) + expect(@knife.session.servers[0][:prefix]).to eq("the.b.org") + end + + it "defaults to a timeout of 120 seconds" do + @knife.session_from_list([["the.b.org", nil, nil]]) + expect(@knife.session.servers[0].options[:timeout]).to eq(120) + end + + it "uses the timeout from the CLI" do + @knife.config = {} + Chef::Config[:knife][:ssh_timeout] = nil + @knife.config[:ssh_timeout] = 5 + @knife.session_from_list([["the.b.org", nil, nil]]) + @knife.merge_configs + expect(@knife.session.servers[0].options[:timeout]).to eq(5) + end + + it "uses the timeout from knife config" do + @knife.config = {} + Chef::Config[:knife][:ssh_timeout] = 6 + @knife.merge_configs + @knife.session_from_list([["the.b.org", nil, nil]]) + expect(@knife.session.servers[0].options[:timeout]).to eq(6) + end + + it "uses the user from an ssh config file" do + @knife.session_from_list([["the.b.org", 123, nil]]) + expect(@knife.session.servers[0].user).to eq("locutus") + end + + it "uses keepalive settings from an ssh config file" do + @knife.session_from_list([["the.b.org", 123, nil]]) + expect(@knife.session.servers[0].options[:keepalive]).to be true + expect(@knife.session.servers[0].options[:keepalive_interval]).to eq 60 + end + end + + describe "#ssh_command" do + let(:execution_channel) { double(:execution_channel, on_data: nil, on_extended_data: nil) } + let(:session_channel) { double(:session_channel, request_pty: nil) } + + let(:execution_channel2) { double(:execution_channel, on_data: nil, on_extended_data: nil) } + let(:session_channel2) { double(:session_channel, request_pty: nil) } + + let(:session) { double(:session, loop: nil) } + + let(:command) { "false" } + + before do + expect(execution_channel) + .to receive(:on_request) + .and_yield(nil, double(:data_stream, read_long: exit_status)) + + expect(session_channel) + .to receive(:exec) + .with(command) + .and_yield(execution_channel, true) + + expect(execution_channel2) + .to receive(:on_request) + .and_yield(nil, double(:data_stream, read_long: exit_status2)) + + expect(session_channel2) + .to receive(:exec) + .with(command) + .and_yield(execution_channel2, true) + + expect(session) + .to receive(:open_channel) + .and_yield(session_channel) + .and_yield(session_channel2) + end + + context "both connections return 0" do + let(:exit_status) { 0 } + let(:exit_status2) { 0 } + + it "returns a 0 exit code" do + expect(@knife.ssh_command(command, session)).to eq(0) + end + end + + context "the first connection returns 1 and the second returns 0" do + let(:exit_status) { 1 } + let(:exit_status2) { 0 } + + it "returns a non-zero exit code" do + expect(@knife.ssh_command(command, session)).to eq(1) + end + end + + context "the first connection returns 1 and the second returns 2" do + let(:exit_status) { 1 } + let(:exit_status2) { 2 } + + it "returns a non-zero exit code" do + expect(@knife.ssh_command(command, session)).to eq(2) + end + end + end + + describe "#tmux" do + before do + ssh_config = { timeout: 50, user: "locutus", port: 23, keepalive: true, keepalive_interval: 60 } + allow(Net::SSH).to receive(:configuration_for).with("foo.example.org", true).and_return(ssh_config) + @query = Chef::Search::Query.new + expect(@query).to receive(:search).and_yield(@node_foo) + allow(Chef::Search::Query).to receive(:new).and_return(@query) + allow(@knife).to receive(:exec).and_return(0) + end + + it "filters out invalid characters from tmux session name" do + @knife.name_args = ["name:foo.example.org", "tmux"] + expect(@knife).to receive(:shell_out!).with("tmux new-session -d -s 'knife ssh name=foo-example-org' -n 'foo.example.org' 'ssh locutus@foo.example.org' ") + @knife.run + end + end + + describe "#run" do + + it "should print usage and exit when a SEARCH QUERY is not provided" do + @knife.name_args = [] + expect(@knife).to receive(:show_usage) + expect(@knife.ui).to receive(:fatal).with(/You must specify the SEARCH QUERY./) + expect { @knife.run }.to raise_error(SystemExit) + end + + context "exit" do + before do + @query = Chef::Search::Query.new + expect(@query).to receive(:search).and_yield(@node_foo) + allow(Chef::Search::Query).to receive(:new).and_return(@query) + allow(@knife).to receive(:ssh_command).and_return(exit_code) + @knife.name_args = ["*:*", "false"] + end + + context "with an error" do + let(:exit_code) { 1 } + + it "should exit with a non-zero exit code" do + expect(@knife).to receive(:exit).with(exit_code) + @knife.run + end + end + + context "with no error" do + let(:exit_code) { 0 } + + it "should not exit" do + expect(@knife).not_to receive(:exit) + @knife.run + end + end + end + end +end diff --git a/knife/spec/unit/knife/ssl_check_spec.rb b/knife/spec/unit/knife/ssl_check_spec.rb new file mode 100644 index 0000000000..4412ee0be9 --- /dev/null +++ b/knife/spec/unit/knife/ssl_check_spec.rb @@ -0,0 +1,256 @@ +# +# Author:: Daniel DeLeo (<dan@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 "stringio" + +describe Chef::Knife::SslCheck do + + let(:name_args) { [] } + let(:stdout_io) { StringIO.new } + let(:stderr_io) { StringIO.new } + + def stderr + stderr_io.string + end + + def stdout + stdout_io.string + end + + subject(:ssl_check) do + s = Chef::Knife::SslCheck.new + allow(s.ui).to receive(:stdout).and_return(stdout_io) + allow(s.ui).to receive(:stderr).and_return(stderr_io) + s.name_args = name_args + s + end + + before do + Chef::Config.chef_server_url = "https://example.com:8443/chef-server" + end + + context "when no arguments are given" do + it "uses the chef_server_url as the host to check" do + expect(ssl_check.host).to eq("example.com") + expect(ssl_check.port).to eq(8443) + end + end + + context "when a specific URI is given" do + let(:name_args) { %w{https://example.test:10443/foo} } + + it "checks the SSL configuration against the given host" do + expect(ssl_check.host).to eq("example.test") + expect(ssl_check.port).to eq(10443) + end + end + + context "when an invalid URI is given" do + + let(:name_args) { %w{foo.test} } + + it "prints an error and exits" do + expect { ssl_check.run }.to raise_error(SystemExit) + expected_stdout = <<~E + USAGE: knife ssl check [URL] (options) + E + expected_stderr = <<~E + ERROR: Given URI: `foo.test' is invalid + E + expect(stdout_io.string).to eq(expected_stdout) + expect(stderr_io.string).to eq(expected_stderr) + end + + context "and its malformed enough to make URI.parse barf" do + + let(:name_args) { %w{ftp://lkj\\blah:example.com/blah} } + + it "prints an error and exits" do + expect { ssl_check.run }.to raise_error(SystemExit) + expected_stdout = <<~E + USAGE: knife ssl check [URL] (options) + E + expected_stderr = <<~E + ERROR: Given URI: `#{name_args[0]}' is invalid + E + expect(stdout_io.string).to eq(expected_stdout) + expect(stderr_io.string).to eq(expected_stderr) + end + end + end + + describe "verifying trusted certificate X509 properties" do + let(:name_args) { %w{https://foo.example.com:8443} } + + let(:trusted_certs_dir) { File.join(CHEF_SPEC_DATA, "trusted_certs") } + let(:trusted_cert_file) { File.join(trusted_certs_dir, "example.crt") } + + let(:store) { OpenSSL::X509::Store.new } + let(:certificate) { OpenSSL::X509::Certificate.new(IO.read(trusted_cert_file)) } + + before do + Chef::Config[:trusted_certs_dir] = trusted_certs_dir + allow(ssl_check).to receive(:trusted_certificates).and_return([trusted_cert_file]) + allow(store).to receive(:add_cert).with(certificate) + allow(OpenSSL::X509::Store).to receive(:new).and_return(store) + allow(OpenSSL::X509::Certificate).to receive(:new).with(IO.read(trusted_cert_file)).and_return(certificate) + allow(ssl_check).to receive(:verify_cert).and_return(true) + allow(ssl_check).to receive(:verify_cert_host).and_return(true) + end + + context "when the trusted certificates directory is not glob escaped", :windows_only do + let(:trusted_certs_dir) { File.join(CHEF_SPEC_DATA.tr("/", "\\"), "trusted_certs") } + + before do + allow(ssl_check).to receive(:trusted_certificates).and_call_original + allow(store).to receive(:verify).with(certificate).and_return(true) + end + + it "escpaes the trusted certificates directory" do + expect(Dir).to receive(:glob) + .with("#{ChefConfig::PathHelper.escape_glob_dir(trusted_certs_dir)}/*.{crt,pem}") + .and_return([trusted_cert_file]) + ssl_check.run + end + end + + context "when the trusted certificates have valid X509 properties" do + before do + allow(store).to receive(:verify).with(certificate).and_return(true) + end + + it "does not generate any X509 warnings" do + expect(ssl_check.ui).not_to receive(:warn).with(/There are invalid certificates in your trusted_certs_dir/) + ssl_check.run + end + end + + context "when the trusted certificates have invalid X509 properties" do + before do + allow(store).to receive(:verify).with(certificate).and_return(false) + allow(store).to receive(:error_string).and_return("unable to get local issuer certificate") + end + + it "generates a warning message with invalid certificate file names" do + expect(ssl_check.ui).to receive(:warn).with(/#{trusted_cert_file}: unable to get local issuer certificate/) + ssl_check.run + end + end + end + + describe "verifying the remote certificate" do + let(:name_args) { %w{https://foo.example.com:8443} } + + let(:tcp_socket) { double(TCPSocket) } + let(:ssl_socket) { double(OpenSSL::SSL::SSLSocket) } + + before do + expect(ssl_check).to receive(:proxified_socket).with("foo.example.com", 8443).and_return(tcp_socket) + expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, ssl_check.verify_peer_ssl_context).and_return(ssl_socket) + end + + def run + ssl_check.run + rescue Exception + # puts "OUT: #{stdout_io.string}" + # puts "ERR: #{stderr_io.string}" + raise + end + + context "when the remote host's certificate is valid" do + + before do + expect(ssl_check).to receive(:verify_X509).and_return(true) # X509 valid certs (no warn) + expect(ssl_socket).to receive(:connect) # no error + expect(ssl_socket).to receive(:post_connection_check).with("foo.example.com") # no error + expect(ssl_socket).to receive(:hostname=).with("foo.example.com") # no error + end + + it "prints a success message" do + ssl_check.run + expect(stdout_io.string).to include("Successfully verified certificates from `foo.example.com'") + end + end + + describe "and the certificate is not valid" do + + let(:tcp_socket_for_debug) { double(TCPSocket) } + let(:ssl_socket_for_debug) { double(OpenSSL::SSL::SSLSocket) } + + let(:self_signed_crt_path) { File.join(CHEF_SPEC_DATA, "trusted_certs", "example.crt") } + let(:self_signed_crt) { OpenSSL::X509::Certificate.new(File.read(self_signed_crt_path)) } + + before do + @old_signal = trap(:INT, "DEFAULT") + + expect(ssl_check).to receive(:proxified_socket) + .with("foo.example.com", 8443) + .and_return(tcp_socket_for_debug) + expect(OpenSSL::SSL::SSLSocket).to receive(:new) + .with(tcp_socket_for_debug, ssl_check.noverify_peer_ssl_context) + .and_return(ssl_socket_for_debug) + end + + after do + trap(:INT, @old_signal) + end + + context "when the certificate's CN does not match the hostname" do + before do + expect(ssl_check).to receive(:verify_X509).and_return(true) # X509 valid certs + expect(ssl_socket).to receive(:connect) # no error + expect(ssl_socket).to receive(:post_connection_check) + .with("foo.example.com") + .and_raise(OpenSSL::SSL::SSLError) + expect(ssl_socket).to receive(:hostname=).with("foo.example.com") # no error + expect(ssl_socket_for_debug).to receive(:connect) + expect(ssl_socket_for_debug).to receive(:peer_cert).and_return(self_signed_crt) + end + + it "shows the CN used by the certificate and prints an error" do + expect { run }.to raise_error(SystemExit) + expect(stderr).to include("The SSL cert is signed by a trusted authority but is not valid for the given hostname") + expect(stderr).to include("You are attempting to connect to: 'foo.example.com'") + expect(stderr).to include("The server's certificate belongs to 'example.local'") + end + + end + + context "when the cert is not signed by any trusted authority" do + before do + expect(ssl_check).to receive(:verify_X509).and_return(true) # X509 valid certs + expect(ssl_socket).to receive(:connect) + .and_raise(OpenSSL::SSL::SSLError) + expect(ssl_socket).to receive(:hostname=) + .with("foo.example.com") # no error + expect(ssl_socket_for_debug).to receive(:connect) + expect(ssl_socket_for_debug).to receive(:peer_cert).and_return(self_signed_crt) + end + + it "shows the CN used by the certificate and prints an error" do + expect { run }.to raise_error(SystemExit) + expect(stderr).to include("The SSL certificate of foo.example.com could not be verified") + end + + end + end + + end + +end diff --git a/knife/spec/unit/knife/ssl_fetch_spec.rb b/knife/spec/unit/knife/ssl_fetch_spec.rb new file mode 100644 index 0000000000..c2dc5bdade --- /dev/null +++ b/knife/spec/unit/knife/ssl_fetch_spec.rb @@ -0,0 +1,222 @@ +# +# Author:: Daniel DeLeo (<dan@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 "chef/knife/ssl_fetch" + +describe Chef::Knife::SslFetch do + + let(:name_args) { [] } + let(:stdout_io) { StringIO.new } + let(:stderr_io) { StringIO.new } + + def stderr + stderr_io.string + end + + def stdout + stdout_io.string + end + + subject(:ssl_fetch) do + s = Chef::Knife::SslFetch.new + s.name_args = name_args + allow(s.ui).to receive(:stdout).and_return(stdout_io) + allow(s.ui).to receive(:stderr).and_return(stderr_io) + s + end + + context "when no arguments are given" do + + before do + Chef::Config.chef_server_url = "https://example.com:8443/chef-server" + end + + it "uses the chef_server_url as the host to fetch" do + expect(ssl_fetch.host).to eq("example.com") + expect(ssl_fetch.port).to eq(8443) + end + end + + context "when a specific URI is given" do + let(:name_args) { %w{https://example.test:10443/foo} } + + it "fetches the SSL configuration against the given host" do + expect(ssl_fetch.host).to eq("example.test") + expect(ssl_fetch.port).to eq(10443) + end + end + + context "when an invalid URI is given" do + + let(:name_args) { %w{foo.test} } + + it "prints an error and exits" do + expect { ssl_fetch.run }.to raise_error(SystemExit) + expected_stdout = <<~E + USAGE: knife ssl fetch [URL] (options) + E + expected_stderr = <<~E + ERROR: Given URI: `foo.test' is invalid + E + expect(stdout_io.string).to eq(expected_stdout) + expect(stderr_io.string).to eq(expected_stderr) + end + + context "and its malformed enough to make URI.parse barf" do + + let(:name_args) { %w{ftp://lkj\\blah:example.com/blah} } + + it "prints an error and exits" do + expect { ssl_fetch.run }.to raise_error(SystemExit) + expected_stdout = <<~E + USAGE: knife ssl fetch [URL] (options) + E + expected_stderr = <<~E + ERROR: Given URI: `#{name_args[0]}' is invalid + E + expect(stdout_io.string).to eq(expected_stdout) + expect(stderr_io.string).to eq(expected_stderr) + end + end + end + + describe "normalizing CNs for use as paths" do + + it "normalizes '*' to 'wildcard'" do + expect(ssl_fetch.normalize_cn("*.example.com")).to eq("wildcard_example_com") + end + + it "normalizes non-alnum and hyphen characters to underscores" do + expect(ssl_fetch.normalize_cn("Billy-Bob's Super Awesome CA!")).to eq("Billy-Bob_s_Super_Awesome_CA_") + end + + end + + describe "#cn_of" do + let(:certificate) { double("Certificate", subject: subject) } + + describe "when the certificate has a common name" do + let(:subject) { [["CN", "common name"]] } + it "returns the common name" do + expect(ssl_fetch.cn_of(certificate)).to eq("common name") + end + end + + describe "when the certificate does not have a common name" do + let(:subject) { [] } + it "returns nil" do + expect(ssl_fetch.cn_of(certificate)).to eq(nil) + end + end + end + + describe "fetching the remote cert chain" do + + let(:name_args) { %w{https://foo.example.com:8443} } + + let(:tcp_socket) { double(TCPSocket) } + let(:ssl_socket) { double(OpenSSL::SSL::SSLSocket) } + + let(:self_signed_crt_path) { File.join(CHEF_SPEC_DATA, "trusted_certs", "example.crt") } + let(:self_signed_crt) { OpenSSL::X509::Certificate.new(File.read(self_signed_crt_path)) } + + let(:trusted_certs_dir) { Dir.mktmpdir } + + def run + ssl_fetch.run + rescue Exception + puts "OUT: #{stdout_io.string}" + puts "ERR: #{stderr_io.string}" + raise + end + + before do + Chef::Config.trusted_certs_dir = trusted_certs_dir + end + + after do + FileUtils.rm_rf(trusted_certs_dir) + end + + context "when the TLS connection is successful" do + + before do + expect(ssl_fetch).to receive(:proxified_socket).with("foo.example.com", 8443).and_return(tcp_socket) + expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, ssl_fetch.noverify_peer_ssl_context).and_return(ssl_socket) + expect(ssl_socket).to receive(:connect) + expect(ssl_socket).to receive(:peer_cert_chain).and_return([self_signed_crt]) + end + + it "fetches the cert chain and writes the certs to the trusted_certs_dir" do + run + stored_cert_path = File.join(trusted_certs_dir, "example_local.crt") + expect(File).to exist(stored_cert_path) + expect(File.read(stored_cert_path)).to eq(File.read(self_signed_crt_path)) + end + + end + + context "when connecting to a non-SSL service (like HTTP)" do + + let(:name_args) { %w{http://foo.example.com} } + + let(:unknown_protocol_error) { OpenSSL::SSL::SSLError.new("SSL_connect returned=1 errno=0 state=SSLv2/v3 read server hello A: unknown protocol") } + + before do + expect(ssl_fetch).to receive(:proxified_socket).with("foo.example.com", 80).and_return(tcp_socket) + expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, ssl_fetch.noverify_peer_ssl_context).and_return(ssl_socket) + expect(ssl_socket).to receive(:connect).and_raise(unknown_protocol_error) + + expect(ssl_fetch).to receive(:exit).with(1) + end + + it "tells the user their URL is for a non-ssl service" do + expected_error_text = <<~ERROR_TEXT + ERROR: The service at the given URI (http://foo.example.com) does not accept SSL connections + ERROR: Perhaps you meant to connect to 'https://foo.example.com'? + ERROR_TEXT + + run + expect(stderr).to include(expected_error_text) + end + + end + + describe "when the certificate does not have a CN" do + let(:self_signed_crt_path) { File.join(CHEF_SPEC_DATA, "trusted_certs", "example_no_cn.crt") } + let(:self_signed_crt) { OpenSSL::X509::Certificate.new(File.read(self_signed_crt_path)) } + + before do + expect(ssl_fetch).to receive(:proxified_socket).with("foo.example.com", 8443).and_return(tcp_socket) + expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, ssl_fetch.noverify_peer_ssl_context).and_return(ssl_socket) + expect(ssl_socket).to receive(:connect) + expect(ssl_socket).to receive(:peer_cert_chain).and_return([self_signed_crt]) + expect(Time).to receive(:new).and_return(1) + end + + it "fetches the certificate and writes it to a file in the trusted_certs_dir" do + run + stored_cert_path = File.join(trusted_certs_dir, "foo.example.com_1.crt") + expect(File).to exist(stored_cert_path) + expect(File.read(stored_cert_path)).to eq(File.read(self_signed_crt_path)) + end + end + + end +end diff --git a/knife/spec/unit/knife/status_spec.rb b/knife/spec/unit/knife/status_spec.rb new file mode 100644 index 0000000000..f3b31c1897 --- /dev/null +++ b/knife/spec/unit/knife/status_spec.rb @@ -0,0 +1,112 @@ +# +# Author:: Sahil Muthoo (<sahil.muthoo@gmail.com>) +# 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" + +describe Chef::Knife::Status do + before(:each) do + node = Chef::Node.new.tap do |n| + n.automatic_attrs["fqdn"] = "foobar" + n.automatic_attrs["ohai_time"] = 1343845969 + n.automatic_attrs["platform"] = "mac_os_x" + n.automatic_attrs["platform_version"] = "10.12.5" + end + allow(Time).to receive(:now).and_return(Time.at(1428573420)) + @query = double("Chef::Search::Query") + allow(@query).to receive(:search).and_yield(node) + allow(Chef::Search::Query).to receive(:new).and_return(@query) + @knife = Chef::Knife::Status.new + @stdout = StringIO.new + allow(@knife.ui).to receive(:stdout).and_return(@stdout) + end + + describe "run" do + let(:opts) do + { filter_result: + { name: ["name"], ipaddress: ["ipaddress"], ohai_time: ["ohai_time"], + cloud: ["cloud"], run_list: ["run_list"], platform: ["platform"], + platform_version: ["platform_version"], chef_environment: ["chef_environment"] } } + end + + it "should default to searching for everything" do + expect(@query).to receive(:search).with(:node, "*:*", opts) + @knife.run + end + + it "should filter by nodes older than some mins" do + @knife.config[:hide_by_mins] = 59 + expect(@query).to receive(:search).with(:node, "NOT ohai_time:[1428569880 TO 1428573420]", opts) + @knife.run + end + + it "should filter by environment" do + @knife.config[:environment] = "production" + expect(@query).to receive(:search).with(:node, "chef_environment:production", opts) + @knife.run + end + + it "should filter by environment and nodes older than some mins" do + @knife.config[:environment] = "production" + @knife.config[:hide_by_mins] = 59 + expect(@query).to receive(:search).with(:node, "chef_environment:production NOT ohai_time:[1428569880 TO 1428573420]", opts) + @knife.run + end + + it "should not use partial search with long output" do + @knife.config[:long_output] = true + expect(@query).to receive(:search).with(:node, "*:*", {}) + @knife.run + end + + context "with a custom query" do + before :each do + @knife.instance_variable_set(:@name_args, ["name:my_custom_name"]) + end + + it "should allow a custom query to be specified" do + expect(@query).to receive(:search).with(:node, "name:my_custom_name", opts) + @knife.run + end + + it "should filter by nodes older than some mins with nodename specified" do + @knife.config[:hide_by_mins] = 59 + expect(@query).to receive(:search).with(:node, "name:my_custom_name NOT ohai_time:[1428569880 TO 1428573420]", opts) + @knife.run + end + + it "should filter by environment with nodename specified" do + @knife.config[:environment] = "production" + expect(@query).to receive(:search).with(:node, "name:my_custom_name AND chef_environment:production", opts) + @knife.run + end + + it "should filter by environment and nodes older than some mins with nodename specified" do + @knife.config[:environment] = "production" + @knife.config[:hide_by_mins] = 59 + expect(@query).to receive(:search).with(:node, "name:my_custom_name AND chef_environment:production NOT ohai_time:[1428569880 TO 1428573420]", opts) + @knife.run + end + end + + it "should not colorize output unless it's writing to a tty" do + @knife.run + expect(@stdout.string.match(/foobar/)).not_to be_nil + expect(@stdout.string.match(/\e.*ago/)).to be_nil + end + end +end diff --git a/knife/spec/unit/knife/supermarket_download_spec.rb b/knife/spec/unit/knife/supermarket_download_spec.rb new file mode 100644 index 0000000000..3796140d61 --- /dev/null +++ b/knife/spec/unit/knife/supermarket_download_spec.rb @@ -0,0 +1,152 @@ +# +# Author:: Thomas Bishop (<bishop.thomas@gmail.com>) +# Copyright:: Copyright 2012-2016, Thomas Bishop +# 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 "chef/knife/supermarket_download" +require "knife_spec_helper" + +describe Chef::Knife::SupermarketDownload do + + describe "run" do + before do + @knife = Chef::Knife::SupermarketDownload.new + @knife.name_args = ["apache2"] + @noauth_rest = double("no auth rest") + @stderr = StringIO.new + @cookbook_api_url = "https://supermarket.chef.io/api/v1/cookbooks" + @version = "1.0.2" + @version_us = @version.tr ".", "_" + @current_data = { "deprecated" => false, + "latest_version" => "#{@cookbook_api_url}/apache2/versions/#{@version_us}", + "replacement" => "other_apache2" } + + allow(@knife.ui).to receive(:stderr).and_return(@stderr) + allow(@knife).to receive(:noauth_rest).and_return(@noauth_rest) + expect(@noauth_rest).to receive(:get) + .with("#{@cookbook_api_url}/apache2") + .and_return(@current_data) + @knife.configure_chef + end + + context "when the cookbook is deprecated and not forced" do + before do + @current_data["deprecated"] = true + end + + it "should warn with info about the replacement" do + expect(@knife.ui).to receive(:warn) + .with(/.+deprecated.+replaced by other_apache2.+/i) + expect(@knife.ui).to receive(:warn) + .with(/use --force.+download.+/i) + @knife.run + end + end + + context "when" do + before do + @cookbook_data = { "version" => @version, + "file" => "http://example.com/apache2_#{@version_us}.tgz" } + @temp_file = double( path: "/tmp/apache2_#{@version_us}.tgz" ) + @file = File.join(Dir.pwd, "apache2-#{@version}.tar.gz") + end + + context "downloading the latest version" do + before do + expect(@noauth_rest).to receive(:get) + .with(@current_data["latest_version"]) + .and_return(@cookbook_data) + expect(@noauth_rest).to receive(:streaming_request) + .with(@cookbook_data["file"]) + .and_return(@temp_file) + end + + context "and it is deprecated and with --force" do + before do + @current_data["deprecated"] = true + @knife.config[:force] = true + end + + it "should download the latest version" do + expect(@knife.ui).to receive(:warn) + .with(/.+deprecated.+replaced by other_apache2.+/i) + expect(FileUtils).to receive(:cp).with(@temp_file.path, @file) + @knife.run + expect(@stderr.string).to match(/downloading apache2.+version.+#{Regexp.escape(@version)}/i) + expect(@stderr.string).to match(/cookbook save.+#{Regexp.escape(@file)}/i) + end + + end + + it "should download the latest version" do + expect(FileUtils).to receive(:cp).with(@temp_file.path, @file) + @knife.run + expect(@stderr.string).to match(/downloading apache2.+version.+#{Regexp.escape(@version)}/i) + expect(@stderr.string).to match(/cookbook save.+#{Regexp.escape(@file)}/i) + end + + context "with -f or --file" do + before do + @file = "/opt/chef/cookbooks/apache2.tar.gz" + @knife.config[:file] = @file + expect(FileUtils).to receive(:cp).with(@temp_file.path, @file) + end + + it "should download the cookbook to the desired file" do + @knife.run + expect(@stderr.string).to match(/downloading apache2.+version.+#{Regexp.escape(@version)}/i) + expect(@stderr.string).to match(/cookbook save.+#{Regexp.escape(@file)}/i) + end + end + + it "should provide an accessor to the version" do + allow(FileUtils).to receive(:cp).and_return(true) + expect(@knife.version).to eq(@version) + @knife.run + end + end + + context "downloading a cookbook of a specific version" do + before do + @version = "1.0.1" + @version_us = @version.tr ".", "_" + @cookbook_data = { "version" => @version, + "file" => "http://example.com/apache2_#{@version_us}.tgz" } + @temp_file = double(path: "/tmp/apache2_#{@version_us}.tgz") + @file = File.join(Dir.pwd, "apache2-#{@version}.tar.gz") + @knife.name_args << @version + end + + it "should download the desired version" do + expect(@noauth_rest).to receive(:get) + .with("#{@cookbook_api_url}/apache2/versions/#{@version_us}") + .and_return(@cookbook_data) + expect(@noauth_rest).to receive(:streaming_request) + .with(@cookbook_data["file"]) + .and_return(@temp_file) + expect(FileUtils).to receive(:cp).with(@temp_file.path, @file) + @knife.run + expect(@stderr.string).to match(/downloading apache2.+version.+#{Regexp.escape(@version)}/i) + expect(@stderr.string).to match(/cookbook save.+#{Regexp.escape(@file)}/i) + end + end + + end + + end + +end diff --git a/knife/spec/unit/knife/supermarket_install_spec.rb b/knife/spec/unit/knife/supermarket_install_spec.rb new file mode 100644 index 0000000000..6ebbbc005c --- /dev/null +++ b/knife/spec/unit/knife/supermarket_install_spec.rb @@ -0,0 +1,203 @@ +# +# Author:: Steven Danna (<steve@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 "chef/knife/supermarket_install" +require "mixlib/archive" + +describe Chef::Knife::SupermarketInstall do + let(:knife) { Chef::Knife::SupermarketInstall.new } + let(:stdout) { StringIO.new } + let(:stderr) { StringIO.new } + let(:downloader) { {} } + let(:archive) { double(Mixlib::Archive, extract: true) } + let(:repo) do + double(sanity_check: true, reset_to_default_state: true, + prepare_to_import: true, finalize_updates_to: true, + merge_updates_from: true) + end + let(:install_path) do + if ChefUtils.windows? + "C:/tmp/chef" + else + "/var/tmp/chef" + end + end + + before(:each) do + require "chef/knife/core/cookbook_scm_repo" + + allow(knife.ui).to receive(:stdout).and_return(stdout) + knife.config = {} + knife.config[:cookbook_path] = [ install_path ] + + allow(knife).to receive(:stderr).and_return(stderr) + allow(knife).to receive(:stdout).and_return(stdout) + + # Assume all external commands would have succeed. :( + allow(File).to receive(:unlink) + allow(File).to receive(:rmtree) + allow(knife).to receive(:shell_out!).and_return(true) + allow(Mixlib::Archive).to receive(:new).and_return(archive) + + # SupermarketDownload Setup + allow(knife).to receive(:download_cookbook_to).and_return(downloader) + allow(downloader).to receive(:version) do + if knife.name_args.size == 2 + knife.name_args[1] + else + "0.3.0" + end + end + + # Stubs for CookbookSCMRepo + allow(Chef::Knife::CookbookSCMRepo).to receive(:new).and_return(repo) + end + + describe "run" do + it "raises an error if a cookbook name is not provided" do + knife.name_args = [] + expect(knife.ui).to receive(:error).with("Please specify a cookbook to download and install.") + expect { knife.run }.to raise_error(SystemExit) + end + + it "raises an error if more than two arguments are given" do + knife.name_args = %w{foo bar baz} + expect(knife.ui).to receive(:error).with("Installing multiple cookbooks at once is not supported.") + expect { knife.run }.to raise_error(SystemExit) + end + + it "raises an error if the second argument is not a version" do + knife.name_args = %w{getting-started 1pass} + expect(knife.ui).to receive(:error).with("Installing multiple cookbooks at once is not supported.") + expect { knife.run }.to raise_error(SystemExit) + end + + it "raises an error if the second argument is a four-digit version" do + knife.name_args = ["getting-started", "0.0.0.1"] + expect(knife.ui).to receive(:error).with("Installing multiple cookbooks at once is not supported.") + expect { knife.run }.to raise_error(SystemExit) + end + + it "raises an error if the second argument is a one-digit version" do + knife.name_args = %w{getting-started 1} + expect(knife.ui).to receive(:error).with("Installing multiple cookbooks at once is not supported.") + expect { knife.run }.to raise_error(SystemExit) + end + + it "installs the specified version if second argument is a three-digit version" do + knife.name_args = ["getting-started", "0.1.0"] + knife.config[:no_deps] = true + upstream_file = File.join(install_path, "getting-started.tar.gz") + expect(knife).to receive(:download_cookbook_to).with(upstream_file) + expect(knife).to receive(:extract_cookbook).with(upstream_file, "0.1.0") + expect(knife).to receive(:clear_existing_files).with(File.join(install_path, "getting-started")) + expect(repo).to receive(:merge_updates_from).with("getting-started", "0.1.0") + knife.run + end + + it "installs the specified version if second argument is a two-digit version" do + knife.name_args = ["getting-started", "0.1"] + knife.config[:no_deps] = true + upstream_file = File.join(install_path, "getting-started.tar.gz") + expect(knife).to receive(:download_cookbook_to).with(upstream_file) + expect(knife).to receive(:extract_cookbook).with(upstream_file, "0.1") + expect(knife).to receive(:clear_existing_files).with(File.join(install_path, "getting-started")) + expect(repo).to receive(:merge_updates_from).with("getting-started", "0.1") + knife.run + end + + it "installs the latest version if only a cookbook name is given" do + knife.name_args = ["getting-started"] + knife.config[:no_deps] = true + upstream_file = File.join(install_path, "getting-started.tar.gz") + expect(knife).to receive(:download_cookbook_to).with(upstream_file) + expect(knife).to receive(:extract_cookbook).with(upstream_file, "0.3.0") + expect(knife).to receive(:clear_existing_files).with(File.join(install_path, "getting-started")) + expect(repo).to receive(:merge_updates_from).with("getting-started", "0.3.0") + knife.run + end + + it "does not create/reset git branches if use_current_branch is set" do + knife.name_args = ["getting-started"] + knife.config[:use_current_branch] = true + knife.config[:no_deps] = true + upstream_file = File.join(install_path, "getting-started.tar.gz") + expect(repo).not_to receive(:prepare_to_import) + expect(repo).not_to receive(:reset_to_default_state) + knife.run + end + + it "does not raise an error if cookbook_path is a string" do + knife.config[:cookbook_path] = install_path + knife.config[:no_deps] = true + knife.name_args = ["getting-started"] + upstream_file = File.join(install_path, "getting-started.tar.gz") + expect(knife).to receive(:download_cookbook_to).with(upstream_file) + expect(knife).to receive(:extract_cookbook).with(upstream_file, "0.3.0") + expect(knife).to receive(:clear_existing_files).with(File.join(install_path, "getting-started")) + expect(repo).to receive(:merge_updates_from).with("getting-started", "0.3.0") + expect { knife.run }.not_to raise_error + end + end # end of run + + let(:metadata) { Chef::Cookbook::Metadata.new } + let(:rb_metadata_path) { File.join(install_path, "post-punk-kitchen", "metadata.rb") } + let(:json_metadata_path) { File.join(install_path, "post-punk-kitchen", "metadata.json") } + + describe "preferred_metadata" do + before do + allow(Chef::Cookbook::Metadata).to receive(:new).and_return(metadata) + allow(File).to receive(:exist?).and_return(false) + knife.instance_variable_set(:@cookbook_name, "post-punk-kitchen") + knife.instance_variable_set(:@install_path, install_path) + end + + it "returns a populated Metadata object if metadata.rb exists" do + allow(File).to receive(:exist?).with(rb_metadata_path).and_return(true) + expect(metadata).to receive(:from_file).with(rb_metadata_path) + knife.preferred_metadata + end + + it "returns a populated Metadata object if metadata.json exists" do + allow(File).to receive(:exist?).with(json_metadata_path).and_return(true) + # expect(IO).to receive(:read).with(json_metadata_path) + allow(IO).to receive(:read) + expect(metadata).to receive(:from_json) + knife.preferred_metadata + end + + it "prefers metadata.rb over metadata.json" do + allow(File).to receive(:exist?).with(rb_metadata_path).and_return(true) + allow(File).to receive(:exist?).with(json_metadata_path).and_return(true) + allow(IO).to receive(:read) + expect(metadata).to receive(:from_file).with(rb_metadata_path) + expect(metadata).not_to receive(:from_json) + knife.preferred_metadata + end + + it "rasies an error if it finds no metadata file" do + expect { knife.preferred_metadata }.to raise_error { |error| + expect(error).to be_a(Chef::Exceptions::MetadataNotFound) + expect(error.cookbook_name).to eq("post-punk-kitchen") + expect(error.install_path).to eq(install_path) + } + end + + end +end diff --git a/knife/spec/unit/knife/supermarket_list_spec.rb b/knife/spec/unit/knife/supermarket_list_spec.rb new file mode 100644 index 0000000000..bbaf733f44 --- /dev/null +++ b/knife/spec/unit/knife/supermarket_list_spec.rb @@ -0,0 +1,70 @@ +# +# Author:: Vivek Singh (<vivek.singh@msystechnologies.com>) +# 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 "chef/knife/supermarket_list" +require "knife_spec_helper" + +describe Chef::Knife::SupermarketList do + let(:knife) { described_class.new } + let(:noauth_rest) { double("no auth rest") } + let(:stdout) { StringIO.new } + let(:cookbooks_data) { + [ + { "cookbook_name" => "1password", "cookbook_maintainer" => "jtimberman", "cookbook_description" => "Installs 1password", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/1password" }, + { "cookbook_name" => "301", "cookbook_maintainer" => "markhuge", "cookbook_description" => "Installs/Configures 301", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/301" }, + { "cookbook_name" => "3cx", "cookbook_maintainer" => "obay", "cookbook_description" => "Installs/Configures 3cx", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/3cx" }, + { "cookbook_name" => "7dtd", "cookbook_maintainer" => "gregf", "cookbook_description" => "Installs/Configures the 7 Days To Die server", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/7dtd" }, + { "cookbook_name" => "7-zip", "cookbook_maintainer" => "sneal", "cookbook_description" => "Installs/Configures the 7-zip file archiver", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/7-zip" }, + ] + } + + let(:response_text) { + { + "start" => 0, + "total" => 5, + "items" => cookbooks_data, + } + } + + describe "run" do + before do + allow(knife.ui).to receive(:stdout).and_return(stdout) + allow(knife).to receive(:noauth_rest).and_return(noauth_rest) + expect(noauth_rest).to receive(:get).and_return(response_text) + knife.configure_chef + end + + it "should display all supermarket cookbooks" do + knife.run + cookbooks_data.each do |item| + expect(stdout.string).to match(/#{item["cookbook_name"]}\s/) + end + end + + describe "with -w or --with-uri" do + it "should display the cookbook uris" do + knife.config[:with_uri] = true + knife.run + cookbooks_data.each do |item| + expect(stdout.string).to match(/#{item["cookbook_name"]}\s/) + expect(stdout.string).to match(/#{item["cookbook"]}\s/) + end + end + end + end +end diff --git a/knife/spec/unit/knife/supermarket_search_spec.rb b/knife/spec/unit/knife/supermarket_search_spec.rb new file mode 100644 index 0000000000..18092f52c8 --- /dev/null +++ b/knife/spec/unit/knife/supermarket_search_spec.rb @@ -0,0 +1,85 @@ +# +# Author:: Vivek Singh (<vivek.singh@msystechnologies.com>) +# 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 "chef/knife/supermarket_search" +require "knife_spec_helper" + +describe Chef::Knife::SupermarketSearch do + let(:knife) { described_class.new } + let(:noauth_rest) { double("no auth rest") } + let(:stdout) { StringIO.new } + let(:cookbooks_data) { + [ + { "cookbook_name" => "mysql", "cookbook_maintainer" => "sous-chefs", "cookbook_description" => "Provides mysql_service, mysql_config, and mysql_client resources", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/mysql" }, + { "cookbook_name" => "mw_mysql", "cookbook_maintainer" => "car", "cookbook_description" => "Installs/Configures mw_mysql", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/mw_mysql" }, + { "cookbook_name" => "L7-mysql", "cookbook_maintainer" => "szelcsanyi", "cookbook_description" => "Installs/Configures MySQL server", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/l7-mysql" }, + { "cookbook_name" => "mysql-sys", "cookbook_maintainer" => "ovaistariq", "cookbook_description" => "Installs the mysql-sys tool. Description of the tool is available here https://github.com/MarkLeith/mysql-sys", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/mysql-sys" }, + { "cookbook_name" => "cg_mysql", "cookbook_maintainer" => "phai", "cookbook_description" => "Installs/Configures mysql with master and slave", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/cg_mysql" }, + ] + } + + let(:response_text) { + { + "start" => 0, + "total" => 5, + "items" => cookbooks_data, + } + } + + let(:empty_response_text) { + { + "start" => 0, + "total" => 0, + "items" => [], + } + } + + describe "run" do + before do + allow(knife.ui).to receive(:stdout).and_return(stdout) + allow(knife).to receive(:noauth_rest).and_return(noauth_rest) + knife.configure_chef + end + + context "when name_args is present" do + before do + expect(noauth_rest).to receive(:get).and_return(response_text) + end + + it "should display cookbooks with given name value" do + knife.name_args = ["mysql"] + knife.run + cookbooks_data.each do |item| + expect(stdout.string).to match(/#{item["cookbook_name"]}\s/) + end + end + end + + context "when name_args is empty string" do + before do + expect(noauth_rest).to receive(:get).and_return(empty_response_text) + end + + it "display nothing with name arg empty string" do + knife.name_args = [""] + knife.run + expect(stdout.string).to eq("\n") + end + end + end +end diff --git a/knife/spec/unit/knife/supermarket_share_spec.rb b/knife/spec/unit/knife/supermarket_share_spec.rb new file mode 100644 index 0000000000..d362edf3c0 --- /dev/null +++ b/knife/spec/unit/knife/supermarket_share_spec.rb @@ -0,0 +1,208 @@ +# +# Author:: Stephen Delano (<stephen@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 "chef/knife/supermarket_share" +require "chef/cookbook_uploader" +require "chef/knife/core/cookbook_site_streaming_uploader" + +describe Chef::Knife::SupermarketShare do + + before(:each) do + @knife = Chef::Knife::SupermarketShare.new + # Merge default settings in. + @knife.merge_configs + @knife.name_args = %w{cookbook_name AwesomeSausage} + + @cookbook = Chef::CookbookVersion.new("cookbook_name") + + @cookbook_loader = double("Chef::CookbookLoader") + allow(@cookbook_loader).to receive(:cookbook_exists?).and_return(true) + allow(@cookbook_loader).to receive(:[]).and_return(@cookbook) + allow(Chef::CookbookLoader).to receive(:new).and_return(@cookbook_loader) + + @noauth_rest = double(Chef::ServerAPI) + allow(@knife).to receive(:noauth_rest).and_return(@noauth_rest) + + @cookbook_uploader = Chef::CookbookUploader.new("herpderp", rest: "norest") + allow(Chef::CookbookUploader).to receive(:new).and_return(@cookbook_uploader) + allow(@cookbook_uploader).to receive(:validate_cookbooks).and_return(true) + allow(Chef::Knife::Core::CookbookSiteStreamingUploader).to receive(:create_build_dir).and_return(Dir.mktmpdir) + + allow(@knife).to receive(:shell_out!).and_return(true) + @stdout = StringIO.new + @stderr = StringIO.new + allow(@knife.ui).to receive(:stdout).and_return(@stdout) + allow(@knife.ui).to receive(:stderr).and_return(@stderr) + end + + describe "run" do + + before(:each) do + allow(@knife).to receive(:do_upload).and_return(true) + @category_response = { + "name" => "cookbook_name", + "category" => "Testing Category", + } + @bad_category_response = { + "error_code" => "NOT_FOUND", + "error_messages" => [ + "Resource does not exist.", + ], + } + end + + it "should set true to config[:dry_run] as default" do + expect(@knife.config[:dry_run]).to be_falsey + end + + it "should should print usage and exit when given no arguments" do + @knife.name_args = [] + expect(@knife).to receive(:show_usage) + expect(@knife.ui).to receive(:fatal) + expect { @knife.run }.to raise_error(SystemExit) + end + + it "should not fail when given only 1 argument and can determine category" do + @knife.name_args = ["cookbook_name"] + expect(@noauth_rest).to receive(:get).with("https://supermarket.chef.io/api/v1/cookbooks/cookbook_name").and_return(@category_response) + expect(@knife).to receive(:do_upload) + @knife.run + end + + it "should use a default category when given only 1 argument and cannot determine category" do + @knife.name_args = ["cookbook_name"] + expect(@noauth_rest).to receive(:get).with("https://supermarket.chef.io/api/v1/cookbooks/cookbook_name") { raise Net::HTTPClientException.new("404 Not Found", OpenStruct.new(code: "404")) } + expect(@knife).to receive(:do_upload) + expect { @knife.run }.to_not raise_error + end + + it "should print error and exit when given only 1 argument and Chef::ServerAPI throws an exception" do + @knife.name_args = ["cookbook_name"] + expect(@noauth_rest).to receive(:get).with("https://supermarket.chef.io/api/v1/cookbooks/cookbook_name") { raise Errno::ECONNREFUSED, "Connection refused" } + expect(@knife.ui).to receive(:fatal) + expect { @knife.run }.to raise_error(SystemExit) + end + + it "should check if the cookbook exists" do + expect(@cookbook_loader).to receive(:cookbook_exists?) + @knife.run + end + + it "should exit and log to error if the cookbook doesn't exist" do + allow(@cookbook_loader).to receive(:cookbook_exists?).and_return(false) + expect(@knife.ui).to receive(:error) + expect { @knife.run }.to raise_error(SystemExit) + end + + if File.exist?("/usr/bin/gnutar") || File.exist?("/bin/gnutar") + it "should use gnutar to make a tarball of the cookbook" do + expect(@knife).to receive(:shell_out!) do |args| + expect(args.to_s).to match(/gnutar -czf/) + end + @knife.run + end + else + it "should make a tarball of the cookbook" do + expect(@knife).to receive(:shell_out!) do |args| + expect(args.to_s).to match(/tar -czf/) + end + @knife.run + end + end + + it "should exit and log to error when the tarball creation fails" do + allow(@knife).to receive(:shell_out!).and_raise(Chef::Exceptions::Exec) + expect(@knife.ui).to receive(:error) + expect { @knife.run }.to raise_error(SystemExit) + end + + it "should upload the cookbook and clean up the tarball" do + expect(@knife).to receive(:do_upload) + expect(FileUtils).to receive(:rm_rf) + @knife.run + end + + context "when the --dry-run flag is specified" do + before do + allow(Chef::Knife::Core::CookbookSiteStreamingUploader).to receive(:create_build_dir).and_return("/var/tmp/dummy") + @knife.config = { dry_run: true } + @so = instance_double("Mixlib::ShellOut") + allow(@knife).to receive(:shell_out!).and_return(@so) + allow(@so).to receive(:stdout).and_return("file") + end + + it "should list files in the tarball" do + allow(@knife).to receive(:tar_cmd).and_return("footar") + expect(@knife).to receive(:shell_out!).with("footar -czf #{@cookbook.name}.tgz #{@cookbook.name}", { cwd: "/var/tmp/dummy" }) + expect(@knife).to receive(:shell_out!).with("footar -tzf #{@cookbook.name}.tgz", { cwd: "/var/tmp/dummy" }) + @knife.run + end + + it "does not upload the cookbook" do + expect(@knife).not_to receive(:do_upload) + @knife.run + end + end + end + + describe "do_upload" do + + before(:each) do + @upload_response = double("Net::HTTPResponse") + allow(Chef::Knife::Core::CookbookSiteStreamingUploader).to receive(:post).and_return(@upload_response) + + allow(File).to receive(:open).and_return(true) + end + + it 'should post the cookbook to "https://supermarket.chef.io"' do + response_text = Chef::JSONCompat.to_json({ uri: "https://supermarket.chef.io/cookbooks/cookbook_name" }) + allow(@upload_response).to receive(:body).and_return(response_text) + allow(@upload_response).to receive(:code).and_return(201) + expect(Chef::Knife::Core::CookbookSiteStreamingUploader).to receive(:post).with(/supermarket\.chef\.io/, anything, anything, anything) + @knife.run + end + + it "should alert the user when a version already exists" do + response_text = Chef::JSONCompat.to_json({ error_messages: ["Version already exists"] }) + allow(@upload_response).to receive(:body).and_return(response_text) + allow(@upload_response).to receive(:code).and_return(409) + expect { @knife.run }.to raise_error(SystemExit) + expect(@stderr.string).to match(/ERROR(.+)cookbook already exists/) + end + + it "should pass any errors on to the user" do + response_text = Chef::JSONCompat.to_json({ error_messages: ["You're holding it wrong"] }) + allow(@upload_response).to receive(:body).and_return(response_text) + allow(@upload_response).to receive(:code).and_return(403) + expect { @knife.run }.to raise_error(SystemExit) + expect(@stderr.string).to match("ERROR(.*)You're holding it wrong") + end + + it "should print the body if no errors are exposed on failure" do + response_text = Chef::JSONCompat.to_json({ system_error: "Your call was dropped", reason: "There's a map for that" }) + allow(@upload_response).to receive(:body).and_return(response_text) + allow(@upload_response).to receive(:code).and_return(500) + expect(@knife.ui).to receive(:error).with(/#{Regexp.escape(response_text)}/) # .ordered + expect(@knife.ui).to receive(:error).with(/Unknown error/) # .ordered + expect { @knife.run }.to raise_error(SystemExit) + end + + end + +end diff --git a/knife/spec/unit/knife/supermarket_unshare_spec.rb b/knife/spec/unit/knife/supermarket_unshare_spec.rb new file mode 100644 index 0000000000..63682a663c --- /dev/null +++ b/knife/spec/unit/knife/supermarket_unshare_spec.rb @@ -0,0 +1,78 @@ +# +# Author:: Stephen Delano (<stephen@chef.io>) +# Author:: Tim Hinderliter (<tim@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 "chef/knife/supermarket_unshare" + +describe Chef::Knife::SupermarketUnshare do + + before(:each) do + @knife = Chef::Knife::SupermarketUnshare.new + @knife.name_args = ["cookbook_name"] + allow(@knife).to receive(:confirm).and_return(true) + + @rest = double("Chef::ServerAPI") + allow(@rest).to receive(:delete).and_return(true) + allow(@knife).to receive(:rest).and_return(@rest) + @stdout = StringIO.new + allow(@knife.ui).to receive(:stdout).and_return(@stdout) + end + + describe "run" do + + describe "with no cookbook argument" do + it "should print the usage and exit" do + @knife.name_args = [] + expect(@knife.ui).to receive(:fatal) + expect(@knife).to receive(:show_usage) + expect { @knife.run }.to raise_error(SystemExit) + end + end + + it "should confirm you want to unshare the cookbook" do + expect(@knife).to receive(:confirm) + @knife.run + end + + it "should send a delete request to the cookbook site" do + expect(@rest).to receive(:delete) + @knife.run + end + + it "should log an error and exit when forbidden" do + exception = double('403 "Forbidden"', code: "403") + allow(@rest).to receive(:delete).and_raise(Net::HTTPClientException.new('403 "Forbidden"', exception)) + expect(@knife.ui).to receive(:error) + expect { @knife.run }.to raise_error(SystemExit) + end + + it "should re-raise any non-forbidden errors on delete" do + exception = double('500 "Application Error"', code: "500") + allow(@rest).to receive(:delete).and_raise(Net::HTTPClientException.new('500 "Application Error"', exception)) + expect { @knife.run }.to raise_error(Net::HTTPClientException) + end + + it "should log a success message" do + expect(@knife.ui).to receive(:info) + @knife.run + end + + end + +end diff --git a/knife/spec/unit/knife/tag_create_spec.rb b/knife/spec/unit/knife/tag_create_spec.rb new file mode 100644 index 0000000000..290b925f4e --- /dev/null +++ b/knife/spec/unit/knife/tag_create_spec.rb @@ -0,0 +1,23 @@ +require "knife_spec_helper" + +describe Chef::Knife::TagCreate do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::TagCreate.new + @knife.name_args = [ Chef::Config[:node_name], "happytag" ] + + @node = Chef::Node.new + allow(@node).to receive :save + allow(Chef::Node).to receive(:load).and_return @node + @stderr = StringIO.new + allow(@knife.ui).to receive(:stderr).and_return(@stderr) + end + + describe "run" do + it "can create tags on a node" do + @knife.run + expect(@node.tags).to eq(["happytag"]) + expect(@stderr.string).to match(/created tags happytag.+node webmonkey.example.com/i) + end + end +end diff --git a/knife/spec/unit/knife/tag_delete_spec.rb b/knife/spec/unit/knife/tag_delete_spec.rb new file mode 100644 index 0000000000..dd01fba50f --- /dev/null +++ b/knife/spec/unit/knife/tag_delete_spec.rb @@ -0,0 +1,25 @@ +require "knife_spec_helper" + +describe Chef::Knife::TagDelete do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::TagDelete.new + @knife.name_args = [ Chef::Config[:node_name], "sadtag" ] + + @node = Chef::Node.new + allow(@node).to receive :save + @node.tags << "sadtag" << "happytag" + allow(Chef::Node).to receive(:load).and_return @node + @stderr = StringIO.new + allow(@knife.ui).to receive(:stderr).and_return(@stderr) + end + + describe "run" do + it "can delete tags on a node" do + expect(@node.tags).to eq(%w{sadtag happytag}) + @knife.run + expect(@node.tags).to eq(["happytag"]) + expect(@stderr.string).to match(/deleted.+sadtag/i) + end + end +end diff --git a/knife/spec/unit/knife/tag_list_spec.rb b/knife/spec/unit/knife/tag_list_spec.rb new file mode 100644 index 0000000000..5da7803e09 --- /dev/null +++ b/knife/spec/unit/knife/tag_list_spec.rb @@ -0,0 +1,23 @@ +require "knife_spec_helper" + +describe Chef::Knife::TagList do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::TagList.new + @knife.name_args = [ Chef::Config[:node_name], "sadtag" ] + + @node = Chef::Node.new + allow(@node).to receive :save + @node.tags << "sadtag" << "happytag" + allow(Chef::Node).to receive(:load).and_return @node + end + + describe "run" do + it "can list tags on a node" do + expected = %w{sadtag happytag} + expect(@node.tags).to eq(expected) + expect(@knife).to receive(:output).with(expected) + @knife.run + end + end +end diff --git a/knife/spec/unit/knife/user_create_spec.rb b/knife/spec/unit/knife/user_create_spec.rb new file mode 100644 index 0000000000..1257d92ee9 --- /dev/null +++ b/knife/spec/unit/knife/user_create_spec.rb @@ -0,0 +1,256 @@ +# +# Author:: Steven Danna (<steve@chef.io>) +# Author:: Tyler Cloke (<tyler@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" + +Chef::Knife::UserCreate.load_deps + +describe Chef::Knife::UserCreate do + + let(:knife) { Chef::Knife::UserCreate.new } + let(:root_rest) { double("Chef::ServerAPI") } + + let(:stderr) do + StringIO.new + end + + let(:stdout) do + StringIO.new + end + + before(:each) do + allow(knife.ui).to receive(:stdout).and_return(stdout) + allow(knife.ui).to receive(:stderr).and_return(stderr) + allow(knife.ui).to receive(:warn) + end + + let(:chef_root_rest_v0) { double("Chef::ServerAPI") } + + context "when USERNAME isn't specified" do + # from spec/support/shared/unit/knife_shared.rb + it_should_behave_like "mandatory field missing" do + let(:name_args) { [] } + let(:fieldname) { "username" } + end + end + + context "when FIRST_NAME isn't specified" do + # from spec/support/shared/unit/knife_shared.rb + it_should_behave_like "mandatory field missing" do + let(:name_args) { %w{some_user some_display_name} } + let(:fieldname) { "first name" } + end + end + + context "when LAST_NAME isn't specified" do + # from spec/support/shared/unit/knife_shared.rb + it_should_behave_like "mandatory field missing" do + let(:name_args) { %w{some_user some_display_name some_first_name} } + let(:fieldname) { "last name" } + end + end + + context "when EMAIL isn't specified" do + # from spec/support/shared/unit/knife_shared.rb + it_should_behave_like "mandatory field missing" do + let(:name_args) { %w{some_user some_display_name some_first_name some_last_name} } + let(:fieldname) { "email" } + end + end + + describe "with prompt password" do + let(:name_args) { %w{some_user some_display_name some_first_name some_last_name test@email.com} } + + before :each do + @user = double("Chef::User") + @key = "You don't come into cooking to get rich - Ramsay" + allow(@user).to receive(:[]).with("private_key").and_return(@key) + knife.config[:passwordprompt] = true + knife.name_args = name_args + end + + it "creates an user" do + expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest) + expect(root_rest).to receive(:post).and_return(@user) + expect(knife.ui).to receive(:ask).with("Please enter the user's password: ", echo: false).and_return("password") + knife.run + end + end + + context "when all mandatory fields are validly specified" do + before do + @user = double("Chef::User") + @key = "You don't come into cooking to get rich - Ramsay" + allow(@user).to receive(:[]).with("private_key").and_return(@key) + knife.name_args = %w{some_user some_display_name some_first_name some_last_name some_email some_password} + end + + it "sets all the mandatory fields" do + expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest) + expect(root_rest).to receive(:post).and_return(@user) + knife.run + expect(knife.user.username).to eq("some_user") + expect(knife.user.display_name).to eq("some_display_name") + expect(knife.user.first_name).to eq("some_first_name") + expect(knife.user.last_name).to eq("some_last_name") + expect(knife.user.email).to eq("some_email") + end + + context "when user_key and prevent_keygen are passed" do + before do + knife.config[:user_key] = "some_key" + knife.config[:prevent_keygen] = true + end + + it "prints the usage" do + expect(knife).to receive(:show_usage) + expect { knife.run }.to raise_error(SystemExit) + end + + it "prints a relevant error message" do + expect { knife.run }.to raise_error(SystemExit) + expect(stderr.string).to match(/You cannot pass --user-key and --prevent-keygen/) + end + end + + context "when --prevent-keygen is passed" do + before do + knife.config[:prevent_keygen] = true + end + + it "does not set user.create_key" do + expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest) + expect(root_rest).to receive(:post).and_return(@user) + knife.run + expect(knife.user.create_key).to be_falsey + end + end + + context "when --prevent-keygen is not passed" do + it "sets user.create_key to true" do + expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest) + expect(root_rest).to receive(:post).and_return(@user) + knife.run + expect(knife.user.create_key).to be_truthy + end + end + + context "when --user-key is passed" do + before do + knife.config[:user_key] = "some_key" + allow(File).to receive(:read).and_return("some_key") + allow(File).to receive(:expand_path) + end + + it "sets user.public_key" do + expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest) + expect(root_rest).to receive(:post).and_return(@user) + knife.run + expect(knife.user.public_key).to eq("some_key") + end + end + + context "when --user-key is not passed" do + it "does not set user.public_key" do + expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest) + expect(root_rest).to receive(:post).and_return(@user) + knife.run + expect(knife.user.public_key).to be_nil + end + end + + describe "with user_name, first_name, last_name, email and password" do + let(:name_args) { %w{some_user some_display_name some_first_name some_last_name test@email.com some_password} } + + before :each do + @user = double("Chef::User") + expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest) + expect(root_rest).to receive(:post).and_return(@user) + @key = "You don't come into cooking to get rich - Ramsay" + allow(@user).to receive(:[]).with("private_key").and_return(@key) + knife.name_args = name_args + end + + it "creates an user" do + expect(knife.ui).to receive(:msg).with(@key) + knife.run + end + + context "with --orgname" do + before :each do + knife.config[:orgname] = "ramsay" + @uri = "http://www.example.com/1" + allow(@user).to receive(:[]).with("uri").and_return(@uri) + end + + let(:request_body) { + { user: "some_user" } + } + + it "creates an user, associates a user, and adds it to the admins group" do + + expect(root_rest).to receive(:post).with("organizations/ramsay/association_requests", request_body).and_return(@user) + expect(root_rest).to receive(:put).with("users/some_user/association_requests/1", { response: "accept" }) + knife.run + end + end + end + + describe "user user_name, --email, --password" do + let(:name_args) { %w{some_user} } + + before :each do + @user = double("Chef::User") + expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest) + expect(root_rest).to receive(:post).and_return(@user) + @key = "You don't come into cooking to get rich - Ramsay" + allow(@user).to receive(:[]).with("private_key").and_return(@key) + knife.name_args = name_args + knife.config[:email] = "test@email.com" + knife.config[:password] = "some_password" + end + + it "creates an user" do + expect(knife.ui).to receive(:msg).with(@key) + knife.run + end + + context "with --orgname" do + before :each do + knife.config[:orgname] = "ramsay" + @uri = "http://www.example.com/1" + allow(@user).to receive(:[]).with("uri").and_return(@uri) + end + + let(:request_body) { + { user: "some_user" } + } + + it "creates an user, associates a user, and adds it to the admins group" do + + expect(root_rest).to receive(:post).with("organizations/ramsay/association_requests", request_body).and_return(@user) + expect(root_rest).to receive(:put).with("users/some_user/association_requests/1", { response: "accept" }) + knife.run + end + end + + end + + end # when all mandatory fields are validly specified +end diff --git a/knife/spec/unit/knife/user_delete_spec.rb b/knife/spec/unit/knife/user_delete_spec.rb new file mode 100644 index 0000000000..57d4072c50 --- /dev/null +++ b/knife/spec/unit/knife/user_delete_spec.rb @@ -0,0 +1,171 @@ +# +# Author:: Steven Danna (<steve@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 "chef/org" + +Chef::Knife::UserDelete.load_deps + +describe Chef::Knife::UserDelete do + subject(:knife) { Chef::Knife::UserDelete.new } + + let(:non_admin_member_org) { Chef::Org.new("non-admin-member") } + let(:solo_admin_member_org) { Chef::Org.new("solo-admin-member") } + let(:shared_admin_member_org) { Chef::Org.new("shared-admin-member") } + + let(:removable_orgs) { [non_admin_member_org, shared_admin_member_org] } + let(:non_removable_orgs) { [solo_admin_member_org] } + + let(:admin_memberships) { [ removable_orgs, non_removable_orgs ] } + let(:username) { "test_user" } + + let(:rest) { double("Chef::ServerAPI") } + let(:orgs) { [non_admin_member_org, solo_admin_member_org, shared_admin_member_org] } + let(:knife) { Chef::Knife::UserDelete.new } + + let(:orgs_data) do + [{ "organization" => { "name" => "non-admin-member" } }, + { "organization" => { "name" => "solo-admin-member" } }, + { "organization" => { "name" => "shared-admin-member" } }, + ] + end + + before(:each) do + allow(Chef::ServerAPI).to receive(:new).and_return(rest) + knife.name_args << username + knife.config[:yes] = true + end + + context "when invoked" do + before do + allow(knife).to receive(:admin_group_memberships).and_return(admin_memberships) + end + + context "with --no-disassociate-user" do + before(:each) do + knife.config[:no_disassociate_user] = true + end + + it "should bypass all checks and go directly to user deletion" do + expect(knife).to receive(:delete_user).with(username) + knife.run + end + end + + context "without --no-disassociate-user" do + before do + allow(knife).to receive(:org_memberships).and_return(orgs) + end + + context "and with --remove-from-admin-groups" do + let(:non_removable_orgs) { [ solo_admin_member_org ] } + before(:each) do + knife.config[:remove_from_admin_groups] = true + end + + context "when an associated user the only organization admin" do + let(:non_removable_orgs) { [ solo_admin_member_org ] } + + it "refuses to proceed with because the user is the only admin" do + expect(knife).to receive(:error_exit_cant_remove_admin_membership!).and_call_original + expect { knife.run }.to raise_error SystemExit + end + end + + context "when an associated user is one of many organization admins" do + let(:non_removable_orgs) { [] } + + it "should remove the user from the group, the org, and then and delete the user" do + expect(knife).to receive(:disassociate_user) + expect(knife).to receive(:remove_from_admin_groups) + expect(knife).to receive(:delete_user) + expect(knife).to receive(:error_exit_cant_remove_admin_membership!).exactly(0).times + expect(knife).to receive(:error_exit_admin_group_member!).exactly(0).times + knife.run + end + + end + end + + context "and without --remove-from-admin-groups" do + before(:each) do + knife.config[:remove_from_admin_groups] = false + end + + context "when an associated user is in admins group" do + let(:removable_orgs) { [ shared_admin_member_org ] } + let(:non_removable_orgs) { [ ] } + it "refuses to proceed with because the user is an admin" do + # Default setup + expect(knife).to receive(:error_exit_admin_group_member!).and_call_original + expect { knife.run }.to raise_error SystemExit + end + end + end + + end + end + + context "#admin_group_memberships" do + before do + expect(non_admin_member_org).to receive(:user_member_of_group?).and_return false + + expect(solo_admin_member_org).to receive(:user_member_of_group?).and_return true + expect(solo_admin_member_org).to receive(:actor_delete_would_leave_admins_empty?).and_return true + + expect(shared_admin_member_org).to receive(:user_member_of_group?).and_return true + expect(shared_admin_member_org).to receive(:actor_delete_would_leave_admins_empty?).and_return false + + end + + it "returns an array of organizations in which the user is an admin, and an array of orgs which block removal" do + expect(knife.admin_group_memberships(orgs, username)).to eq [ [solo_admin_member_org, shared_admin_member_org], [solo_admin_member_org]] + end + end + + context "#delete_user" do + it "attempts to delete the user from the system via DELETE to the /users endpoint" do + expect(rest).to receive(:delete).with("users/#{username}") + knife.delete_user(username) + end + end + + context "#disassociate_user" do + it "attempts to remove dissociate the user from each org" do + removable_orgs.each { |org| expect(org).to receive(:dissociate_user).with(username) } + knife.disassociate_user(removable_orgs, username) + end + end + + context "#remove_from_admin_groups" do + it "attempts to remove the given user from the organizations' groups" do + removable_orgs.each { |org| expect(org).to receive(:remove_user_from_group).with("admins", username) } + knife.remove_from_admin_groups(removable_orgs, username) + end + end + + context "#org_memberships" do + it "should make a REST request to return the list of organizations that the user is a member of" do + expect(rest).to receive(:get).with("users/test_user/organizations").and_return orgs_data + result = knife.org_memberships(username) + result.each_with_index do |v, x| + expect(v.to_hash).to eq(orgs[x].to_hash) + end + end + end +end diff --git a/knife/spec/unit/knife/user_edit_spec.rb b/knife/spec/unit/knife/user_edit_spec.rb new file mode 100644 index 0000000000..12e2f19561 --- /dev/null +++ b/knife/spec/unit/knife/user_edit_spec.rb @@ -0,0 +1,54 @@ +# +# Author:: Steven Danna (<steve@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" + +describe Chef::Knife::UserEdit do + let(:knife) { Chef::Knife::UserEdit.new } + let(:root_rest) { double("Chef::ServerAPI") } + + before(:each) do + @stderr = StringIO.new + @stdout = StringIO.new + allow(knife.ui).to receive(:stderr).and_return(@stderr) + allow(knife.ui).to receive(:stdout).and_return(@stdout) + knife.name_args = [ "my_user2" ] + knife.config[:disable_editing] = true + end + + it "loads and edits the user" do + data = { "username" => "my_user2" } + edited_data = { "username" => "edit_user2" } + result = {} + @key = "You don't come into cooking to get rich - Ramsay" + allow(result).to receive(:[]).with("private_key").and_return(@key) + + expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest) + expect(root_rest).to receive(:get).with("users/my_user2").and_return(data) + expect(knife).to receive(:get_updated_user).with(data).and_return(edited_data) + expect(root_rest).to receive(:put).with("users/my_user2", edited_data).and_return(result) + knife.run + end + + it "prints usage and exits when a user name is not provided" do + knife.name_args = [] + expect(knife).to receive(:show_usage) + expect(knife.ui).to receive(:fatal) + expect { knife.run }.to raise_error(SystemExit) + end +end diff --git a/knife/spec/unit/knife/user_list_spec.rb b/knife/spec/unit/knife/user_list_spec.rb new file mode 100644 index 0000000000..01013de352 --- /dev/null +++ b/knife/spec/unit/knife/user_list_spec.rb @@ -0,0 +1,73 @@ +# +# Author:: Steven Danna +# 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" + +Chef::Knife::UserList.load_deps + +describe Chef::Knife::UserList do + + let(:knife) { Chef::Knife::UserList.new } + let(:users) do + { + "user1" => "http//test/users/user1", + "user2" => "http//test/users/user2", + } + end + + before :each do + @rest = double("Chef::ServerAPI") + allow(Chef::ServerAPI).to receive(:new).and_return(@rest) + allow(@rest).to receive(:get).with("users").and_return(users) + end + + describe "with no arguments" do + it "lists all non users" do + expect(knife.ui).to receive(:output).with(%w{user1 user2}) + knife.run + end + + end + + describe "with all_users argument" do + before do + knife.config[:all_users] = true + end + + it "lists all users including hidden users" do + expect(knife.ui).to receive(:output).with(%w{user1 user2}) + knife.run + end + end + + it "lists the users" do + expect(knife).to receive(:format_list_for_display) + knife.run + end + + describe "with options with_uri argument" do + before do + knife.config[:with_uri] = true + end + + it "lists all users including hidden users" do + expect(knife.ui).to receive(:output).with(users) + knife.run + end + end +end diff --git a/knife/spec/unit/knife/user_password_spec.rb b/knife/spec/unit/knife/user_password_spec.rb new file mode 100644 index 0000000000..139ed242de --- /dev/null +++ b/knife/spec/unit/knife/user_password_spec.rb @@ -0,0 +1,64 @@ +# +# Author:: Snehal Dwivedi (<sdwivedi@msystechnologies.com>) +# Copyright:: Copyright 2014-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 "knife_spec_helper" +require "chef/org" + +Chef::Knife::UserDelete.load_deps + +describe Chef::Knife::UserPassword do + + let(:root_rest) { double("Chef::ServerAPI") } + + before :each do + @knife = Chef::Knife::UserPassword.new + @user_name = "foobar" + @password = "abc123" + @user = double("Chef::User") + allow(@user).to receive(:root_rest).and_return(root_rest) + @key = "You don't come into cooking to get rich - Ramsay" + end + + describe "should change user's password" do + before :each do + @knife.name_args << @user_name << @password + end + + it "with username and password" do + result = { "password" => [], "recovery_authentication_enabled" => true } + allow(@user).to receive(:[]).with("organization") + + expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest) + expect(@user.root_rest).to receive(:get).with("users/#{@user_name}").and_return(result) + expect(@user.root_rest).to receive(:put).with("users/#{@user_name}", result) + expect(@knife.ui).to receive(:msg).with("Authentication info updated for #{@user_name}.") + + @knife.run + end + end + + describe "should not change user's password" do + + it "ails with an informative message" do + expect(@knife).to receive(:show_usage) + expect(@knife.ui).to receive(:fatal).with("You must pass two arguments") + expect(@knife.ui).to receive(:fatal).with("Note that --enable-external-auth cannot be passed with a password") + expect { @knife.run }.to raise_error(SystemExit) + end + end +end diff --git a/knife/spec/unit/knife/user_reregister_spec.rb b/knife/spec/unit/knife/user_reregister_spec.rb new file mode 100644 index 0000000000..9178996abf --- /dev/null +++ b/knife/spec/unit/knife/user_reregister_spec.rb @@ -0,0 +1,56 @@ +# +# Author:: Steven Danna (<steve@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" + +describe Chef::Knife::UserReregister do + let(:knife) { Chef::Knife::UserReregister.new } + let(:user_mock) { double("user_mock", private_key: "private_key") } + let(:stdout) { StringIO.new } + + before do + Chef::Knife::UserReregister.load_deps + knife.name_args = [ "a_user" ] + allow(Chef::UserV1).to receive(:load).and_return(user_mock) + allow(knife.ui).to receive(:stdout).and_return(stdout) + allow(knife.ui).to receive(:stderr).and_return(stdout) + allow(user_mock).to receive(:username).and_return("a_user") + end + + it "prints usage and exits when a user name is not provided" do + knife.name_args = [] + expect(knife).to receive(:show_usage) + expect(knife.ui).to receive(:fatal) + expect { knife.run }.to raise_error(SystemExit) + end + + it "reregisters the user and prints the key" do + expect(user_mock).to receive(:reregister).and_return(user_mock) + knife.run + expect(stdout.string).to match( /private_key/ ) + end + + it "writes the private key to a file when --file is specified" do + expect(user_mock).to receive(:reregister).and_return(user_mock) + knife.config[:file] = "/tmp/a_file" + filehandle = StringIO.new + expect(File).to receive(:open).with("/tmp/a_file", "w").and_yield(filehandle) + knife.run + expect(filehandle.string).to eq("private_key") + end +end diff --git a/knife/spec/unit/knife/user_show_spec.rb b/knife/spec/unit/knife/user_show_spec.rb new file mode 100644 index 0000000000..3bcbbcb648 --- /dev/null +++ b/knife/spec/unit/knife/user_show_spec.rb @@ -0,0 +1,91 @@ +# +# Author:: Steven Danna (<steve@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 "chef/org" + +Chef::Knife::UserShow.load_deps + +describe Chef::Knife::UserShow do + let(:knife) { Chef::Knife::UserShow.new } + let(:user_mock) { double("user_mock") } + let(:root_rest) { double("Chef::ServerAPI") } + + before :each do + @user_name = "foobar" + @password = "abc123" + @user = double("Chef::User") + allow(@user).to receive(:root_rest).and_return(root_rest) + # allow(Chef::User).to receive(:new).and_return(@user) + @key = "You don't come into cooking to get rich - Ramsay" + end + + describe "withot organisation argument" do + before do + knife.name_args = [ "my_user" ] + allow(user_mock).to receive(:username).and_return("my_user") + end + + it "should load the user" do + expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest) + expect(@user.root_rest).to receive(:get).with("users/my_user") + knife.run + end + + it "loads and displays the user" do + expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest) + expect(@user.root_rest).to receive(:get).with("users/my_user") + expect(knife).to receive(:format_for_display) + knife.run + end + + it "prints usage and exits when a user name is not provided" do + knife.name_args = [] + expect(knife).to receive(:show_usage) + expect(knife.ui).to receive(:fatal) + expect { knife.run }.to raise_error(SystemExit) + end + end + + describe "with organisation argument" do + before :each do + @user_name = "foobar" + @org_name = "abc_org" + knife.name_args << @user_name << @org_name + @org = double("Chef::Org") + allow(Chef::Org).to receive(:new).and_return(@org) + @key = "You don't come into cooking to get rich - Ramsay" + end + + let(:orgs) do + [@org] + end + + it "should load the user with organisation" do + + result = { "organizations" => [] } + knife.config[:with_orgs] = true + + expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest) + allow(@org).to receive(:[]).with("organization").and_return({ "name" => "test" }) + expect(@user.root_rest).to receive(:get).with("users/#{@user_name}").and_return(result) + expect(@user.root_rest).to receive(:get).with("users/#{@user_name}/organizations").and_return(orgs) + knife.run + end + end +end diff --git a/knife/spec/unit/knife_spec.rb b/knife/spec/unit/knife_spec.rb new file mode 100644 index 0000000000..f3315f3cf5 --- /dev/null +++ b/knife/spec/unit/knife_spec.rb @@ -0,0 +1,634 @@ +# +# Author:: Adam Jacob (<adam@chef.io>) +# Author:: Tim Hinderliter (<tim@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. +# + +# Fixtures for subcommand loading live in this namespace +module KnifeSpecs +end + +require "knife_spec_helper" +require "uri" +require "chef/knife/core/gem_glob_loader" + +describe Chef::Knife do + + let(:stderr) { StringIO.new } + + let(:knife) { Chef::Knife.new } + + let(:config_location) { File.expand_path("~/.chef/config.rb") } + + let(:config_loader) do + instance_double("WorkstationConfigLoader", + load: nil, no_config_found?: false, + config_location: config_location, + chef_config_dir: "/etc/chef") + end + + before(:each) do + Chef::Log.logger = Logger.new(StringIO.new) + + Chef::Config[:node_name] = "webmonkey.example.com" + + allow(Chef::WorkstationConfigLoader).to receive(:new).and_return(config_loader) + allow(config_loader).to receive(:explicit_config_file=) + allow(config_loader).to receive(:profile=) + + # Prevent gratuitous code reloading: + allow(Chef::Knife).to receive(:load_commands) + allow(knife.ui).to receive(:puts) + allow(knife.ui).to receive(:print) + allow(Chef::Log).to receive(:init) + allow(Chef::Log).to receive(:level) + %i{debug info warn error crit}.each do |level_sym| + allow(Chef::Log).to receive(level_sym) + end + allow(Chef::Knife).to receive(:puts) + end + + after(:each) do + Chef::Knife.reset_config_loader! + end + + it "does not reset Chef::Config[:verbosity to nil if config[:verbosity] is nil" do + Chef::Config[:verbosity] = 2 + Chef::Knife.new + expect(Chef::Config[:verbosity]).to eq(2) + end + + describe "after loading a subcommand" do + before do + Chef::Knife.reset_subcommands! + + if KnifeSpecs.const_defined?(:TestNameMapping) + KnifeSpecs.send(:remove_const, :TestNameMapping) + end + + if KnifeSpecs.const_defined?(:TestExplicitCategory) + KnifeSpecs.send(:remove_const, :TestExplicitCategory) + end + + Kernel.load(File.join(CHEF_SPEC_DATA, "knife_subcommand", "test_name_mapping.rb")) + Kernel.load(File.join(CHEF_SPEC_DATA, "knife_subcommand", "test_explicit_category.rb")) + end + + it "has a category based on its name" do + expect(KnifeSpecs::TestNameMapping.subcommand_category).to eq("test") + end + + it "has an explicitly defined category if set" do + expect(KnifeSpecs::TestExplicitCategory.subcommand_category).to eq("cookbook site") + end + + it "can reference the subcommand by its snake cased name" do + expect(Chef::Knife.subcommands["test_name_mapping"]).to equal(KnifeSpecs::TestNameMapping) + end + + it "lists subcommands by category" do + expect(Chef::Knife.subcommands_by_category["test"]).to include("test_name_mapping") + end + + it "lists subcommands by category when the subcommands have explicit categories" do + expect(Chef::Knife.subcommands_by_category["cookbook site"]).to include("test_explicit_category") + end + + it "has empty dependency_loader list by default" do + expect(KnifeSpecs::TestNameMapping.dependency_loaders).to be_empty + end + end + + describe "after loading all subcommands" do + before do + Chef::Knife.reset_subcommands! + Chef::Knife.load_commands + end + + it "references a subcommand class by its snake cased name" do + class SuperAwesomeCommand < Chef::Knife + end + + Chef::Knife.load_commands + + expect(Chef::Knife.subcommands).to have_key("super_awesome_command") + expect(Chef::Knife.subcommands["super_awesome_command"]).to eq(SuperAwesomeCommand) + end + + it "records the location of ChefFS-based commands correctly" do + class AwesomeCheffsCommand < Chef::ChefFS::Knife + end + + Chef::Knife.load_commands + expect(Chef::Knife.subcommand_files["awesome_cheffs_command"]).to eq([__FILE__]) + end + + it "guesses a category from a given ARGV" do + Chef::Knife.subcommands_by_category["cookbook"] << :cookbook + Chef::Knife.subcommands_by_category["cookbook site"] << :cookbook_site + expect(Chef::Knife.guess_category(%w{cookbook foo bar baz})).to eq("cookbook") + expect(Chef::Knife.guess_category(%w{cookbook site foo bar baz})).to eq("cookbook site") + expect(Chef::Knife.guess_category(%w{cookbook site --help})).to eq("cookbook site") + end + + it "finds a subcommand class based on ARGV" do + Chef::Knife.subcommands["cookbook_site_install"] = :CookbookSiteInstall + Chef::Knife.subcommands["cookbook"] = :Cookbook + expect(Chef::Knife.subcommand_class_from(%w{cookbook site install --help foo bar baz})).to eq(:CookbookSiteInstall) + end + + it "special case sets the subcommand_loader to GemGlobLoader when running rehash" do + Chef::Knife.subcommands["rehash"] = :Rehash + expect(Chef::Knife.subcommand_class_from(%w{rehash })).to eq(:Rehash) + expect(Chef::Knife.subcommand_loader).to be_a(Chef::Knife::SubcommandLoader::GemGlobLoader) + end + + end + + describe "the headers include X-Remote-Request-Id" do + + let(:headers) do + { "Accept" => "application/json", + "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", + "X-Chef-Version" => Chef::VERSION, + "Host" => "api.opscode.piab", + "X-REMOTE-REQUEST-ID" => request_id, + } + end + + let(:request_id) { "1234" } + + let(:request_mock) { {} } + + let(:rest) do + allow(Net::HTTP).to receive(:new).and_return(http_client) + allow(Chef::RequestID.instance).to receive(:request_id).and_return(request_id) + allow(Chef::Config).to receive(:chef_server_url).and_return("https://api.opscode.piab") + command = Chef::Knife.run(%w{test yourself}) + rest = command.noauth_rest + rest + end + + let!(:http_client) do + http_client = Net::HTTP.new(url.host, url.port) + allow(http_client).to receive(:request).and_yield(http_response).and_return(http_response) + http_client + end + + let(:url) { URI.parse("https://api.opscode.piab") } + + let(:http_response) do + http_response = Net::HTTPSuccess.new("1.1", "200", "successful rest req") + allow(http_response).to receive(:read_body) + allow(http_response).to receive(:body).and_return(body) + http_response["Content-Length"] = body.bytesize.to_s + http_response + end + + let(:body) { "ninja" } + + before(:each) do + Chef::Config[:chef_server_url] = "https://api.opscode.piab" + if KnifeSpecs.const_defined?(:TestYourself) + KnifeSpecs.send :remove_const, :TestYourself + end + Kernel.load(File.join(CHEF_SPEC_DATA, "knife_subcommand", "test_yourself.rb")) + Chef::Knife.subcommands.each { |name, klass| Chef::Knife.subcommands.delete(name) unless klass.is_a?(Class) } + end + + it "confirms that the headers include X-Remote-Request-Id" do + expect(Net::HTTP::Get).to receive(:new).with("/monkey", headers).and_return(request_mock) + rest.get("monkey") + end + end + + describe "when running a command" do + before(:each) do + if KnifeSpecs.const_defined?(:TestYourself) + KnifeSpecs.send :remove_const, :TestYourself + end + Kernel.load(File.join(CHEF_SPEC_DATA, "knife_subcommand", "test_yourself.rb")) + Chef::Knife.subcommands.each { |name, klass| Chef::Knife.subcommands.delete(name) unless klass.is_a?(Class) } + end + + it "merges the global knife CLI options" do + extra_opts = {} + extra_opts[:editor] = { long: "--editor EDITOR", + description: "Set the editor to use for interactive commands", + short: "-e EDITOR", + default: "/usr/bin/vim" } + + # there is special hackery to return the subcommand instance going on here. + command = Chef::Knife.run(%w{test yourself}, extra_opts) + editor_opts = command.options[:editor] + expect(editor_opts[:long]).to eq("--editor EDITOR") + expect(editor_opts[:description]).to eq("Set the editor to use for interactive commands") + expect(editor_opts[:short]).to eq("-e EDITOR") + expect(editor_opts[:default]).to eq("/usr/bin/vim") + end + + it "creates an instance of the subcommand and runs it" do + command = Chef::Knife.run(%w{test yourself}) + expect(command).to be_an_instance_of(KnifeSpecs::TestYourself) + expect(command.ran).to be_truthy + end + + it "passes the command specific args to the subcommand" do + command = Chef::Knife.run(%w{test yourself with some args}) + expect(command.name_args).to eq(%w{with some args}) + end + + it "excludes the command name from the name args when parts are joined with underscores" do + command = Chef::Knife.run(%w{test_yourself with some args}) + expect(command.name_args).to eq(%w{with some args}) + end + + it "exits if no subcommand matches the CLI args" do + stdout = StringIO.new + + allow(Chef::Knife.ui).to receive(:stderr).and_return(stderr) + allow(Chef::Knife.ui).to receive(:stdout).and_return(stdout) + expect(Chef::Knife.ui).to receive(:fatal) + expect { Chef::Knife.run(%w{fuuu uuuu fuuuu}) }.to raise_error(SystemExit) { |e| expect(e.status).not_to eq(0) } + end + + it "loads lazy dependencies" do + Chef::Knife.run(%w{test yourself}) + expect(KnifeSpecs::TestYourself.test_deps_loaded).to be_truthy + end + + it "loads lazy dependencies from multiple deps calls" do + other_deps_loaded = false + KnifeSpecs::TestYourself.class_eval do + deps { other_deps_loaded = true } + end + + Chef::Knife.run(%w{test yourself}) + expect(KnifeSpecs::TestYourself.test_deps_loaded).to be_truthy + expect(other_deps_loaded).to be_truthy + end + + describe "working with unmerged configuration in #config_source" do + let(:command) { KnifeSpecs::TestYourself.new([]) } + + before do + KnifeSpecs::TestYourself.option(:opt_with_default, + short: "-D VALUE", + default: "default-value") + end + # This supports a use case used by plugins, where the pattern + # seems to follow: + # cmd = KnifeCommand.new + # cmd.config[:config_key] = value + # cmd.run + # + # This bypasses Knife::run and the `merge_configs` call it + # performs - config_source should break when that happens. + context "when config is fed in directly without a merge" do + it "retains the value but returns nil as a config source" do + command.config[:test1] = "value" + expect(command.config[:test1]).to eq "value" + expect(command.config_source(:test1)).to eq nil + end + end + + end + describe "merging configuration options" do + before do + KnifeSpecs::TestYourself.option(:opt_with_default, + short: "-D VALUE", + default: "default-value") + end + + it "sets the default log_location to STDERR for Chef::Log warnings" do + knife_command = KnifeSpecs::TestYourself.new([]) + knife_command.configure_chef + expect(Chef::Config[:log_location]).to eq(STDERR) + end + + it "sets the default log_level to warn so we can issue Chef::Log.warn" do + knife_command = KnifeSpecs::TestYourself.new([]) + knife_command.configure_chef + expect(Chef::Config[:log_level]).to eql(:warn) + end + + it "prefers the default value from option definition if no config or command line value is present and reports the source as default" do + knife_command = KnifeSpecs::TestYourself.new([]) # empty argv + knife_command.configure_chef + expect(knife_command.config[:opt_with_default]).to eq("default-value") + expect(knife_command.config_source(:opt_with_default)).to eq(:cli_default) + end + + it "prefers a value in Chef::Config[:knife] to the default and reports the source as config" do + Chef::Config[:knife][:opt_with_default] = "from-knife-config" + knife_command = KnifeSpecs::TestYourself.new([]) # empty argv + knife_command.configure_chef + expect(knife_command.config[:opt_with_default]).to eq("from-knife-config") + expect(knife_command.config_source(:opt_with_default)).to eq(:config) + end + + it "prefers a value from command line over Chef::Config and the default and reports the source as CLI" do + knife_command = KnifeSpecs::TestYourself.new(["-D", "from-cli"]) + knife_command.configure_chef + expect(knife_command.config[:opt_with_default]).to eq("from-cli") + expect(knife_command.config_source(:opt_with_default)).to eq(:cli) + end + + it "merges `listen` config to Chef::Config" do + knife_command = Chef::Knife.run(%w{test yourself --no-listen}, Chef::Application::Knife.options) + expect(Chef::Config[:listen]).to be(false) + expect(knife_command.config_source(:listen)).to eq(:cli) + end + + it "merges Chef::Config[:knife] values into the config hash even if they have no cli keys" do + Chef::Config[:knife][:opt_with_no_cli_key] = "from-knife-config" + knife_command = KnifeSpecs::TestYourself.new([]) # empty argv + knife_command.configure_chef + expect(knife_command.config[:opt_with_no_cli_key]).to eq("from-knife-config") + expect(knife_command.config_source(:opt_with_no_cli_key)).to eq(:config) + end + + it "merges Chef::Config[:knife] default values into the config hash even if they have no cli keys" do + Chef::Config.config_context :knife do + default :opt_with_no_cli_key, "from-knife-default" + end + knife_command = KnifeSpecs::TestYourself.new([]) # empty argv + knife_command.configure_chef + expect(knife_command.config[:opt_with_no_cli_key]).to eq("from-knife-default") + expect(knife_command.config_source(:opt_with_no_cli_key)).to eq(:config_default) + end + + context "verbosity is one" do + let(:fake_config) { "/does/not/exist/knife.rb" } + + before do + knife.config[:verbosity] = 1 + knife.config[:config_file] = fake_config + config_loader = double("Chef::WorkstationConfigLoader", load: true, no_config_found?: false, chef_config_dir: "/etc/chef", config_location: fake_config) + allow(config_loader).to receive(:explicit_config_file=).with(fake_config).and_return(fake_config) + allow(config_loader).to receive(:profile=) + allow(Chef::WorkstationConfigLoader).to receive(:new).and_return(config_loader) + end + + it "prints the path to the configuration file used" do + stdout, stderr, stdin = StringIO.new, StringIO.new, StringIO.new + knife.ui = Chef::Knife::UI.new(stdout, stderr, stdin, {}) + expect(Chef::Log).to receive(:info).with("Using configuration from #{fake_config}") + knife.configure_chef + end + end + + # -VV (2) is debug, -VVV (3) is trace + [ 2, 3 ].each do |verbosity| + it "does not humanize the exception if Chef::Config[:verbosity] is #{verbosity}" do + Chef::Config[:verbosity] = verbosity + allow(knife).to receive(:run).and_raise(Exception) + expect(knife).not_to receive(:humanize_exception) + expect { knife.run_with_pretty_exceptions }.to raise_error(Exception) + end + end + end + + describe "setting arbitrary configuration with --config-option" do + + let(:stdout) { StringIO.new } + + let(:stderr) { StringIO.new } + + let(:stdin) { StringIO.new } + + let(:ui) { Chef::Knife::UI.new(stdout, stderr, stdin, disable_editing: true) } + + let(:subcommand) do + KnifeSpecs::TestYourself.options = Chef::Application::Knife.options.merge(KnifeSpecs::TestYourself.options) + KnifeSpecs::TestYourself.new(%w{--config-option badly_formatted_arg}).tap do |cmd| + cmd.ui = ui + end + end + + it "sets arbitrary configuration via --config-option" do + Chef::Knife.run(%w{test yourself --config-option arbitrary_config_thing=hello}, Chef::Application::Knife.options) + expect(Chef::Config[:arbitrary_config_thing]).to eq("hello") + end + + it "handles errors in arbitrary configuration" do + expect(subcommand).to receive(:exit).with(1) + subcommand.configure_chef + expect(stderr.string).to include("ERROR: Unparsable config option \"badly_formatted_arg\"") + expect(stdout.string).to include(subcommand.opt_parser.to_s) + end + end + + end + + describe "when first created" do + + let(:knife) { + Kernel.load "spec/data/knife_subcommand/test_yourself.rb" + KnifeSpecs::TestYourself.new(%w{with some args -s scrogramming}) + } + + it "it parses the options passed to it" do + expect(knife.config[:scro]).to eq("scrogramming") + end + + it "extracts its command specific args from the full arg list" do + expect(knife.name_args).to eq(%w{with some args}) + end + + it "does not have lazy dependencies loaded" do + skip "unstable with randomization... prolly needs more isolation" + + expect(knife.class.test_deps_loaded).not_to be_truthy + end + end + + describe "when formatting exceptions" do + + let(:stdout) { StringIO.new } + let(:stderr) { StringIO.new } + let(:stdin) { StringIO.new } + + let(:ui) { Chef::Knife::UI.new(stdout, stderr, stdin, {}) } + + before do + knife.ui = ui + expect(knife).to receive(:exit).with(100) + end + + it "formats 401s nicely" do + response = Net::HTTPUnauthorized.new("1.1", "401", "Unauthorized") + response.instance_variable_set(:@read, true) # I hate you, net/http. + allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "y u no syncronize your clock?")) + allow(knife).to receive(:run).and_raise(Net::HTTPClientException.new("401 Unauthorized", response)) + knife.run_with_pretty_exceptions + expect(stderr.string).to match(/ERROR: Failed to authenticate to/) + expect(stderr.string).to match(/Response: y u no syncronize your clock\?/) + end + + it "formats 403s nicely" do + response = Net::HTTPForbidden.new("1.1", "403", "Forbidden") + response.instance_variable_set(:@read, true) # I hate you, net/http. + allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "y u no administrator")) + allow(knife).to receive(:run).and_raise(Net::HTTPClientException.new("403 Forbidden", response)) + allow(knife).to receive(:username).and_return("sadpanda") + knife.run_with_pretty_exceptions + expect(stderr.string).to match(/ERROR: You authenticated successfully to http.+ as sadpanda but you are not authorized for this action/) + expect(stderr.string).to match(/Response: y u no administrator/) + end + + context "when proxy servers are set" do + before do + ENV["http_proxy"] = "xyz" + end + + after do + ENV.delete("http_proxy") + end + + it "formats proxy errors nicely" do + response = Net::HTTPForbidden.new("1.1", "403", "Forbidden") + response.instance_variable_set(:@read, true) + allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "y u no administrator")) + allow(knife).to receive(:run).and_raise(Net::HTTPClientException.new("403 Forbidden", response)) + allow(knife).to receive(:username).and_return("sadpanda") + knife.run_with_pretty_exceptions + expect(stderr.string).to match(/ERROR: You authenticated successfully to http.+ as sadpanda but you are not authorized for this action/) + expect(stderr.string).to match(/ERROR: There are proxy servers configured, your server url may need to be added to NO_PROXY./) + expect(stderr.string).to match(/Response: y u no administrator/) + end + end + + it "formats 400s nicely" do + response = Net::HTTPBadRequest.new("1.1", "400", "Bad Request") + response.instance_variable_set(:@read, true) # I hate you, net/http. + allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "y u search wrong")) + allow(knife).to receive(:run).and_raise(Net::HTTPClientException.new("400 Bad Request", response)) + knife.run_with_pretty_exceptions + expect(stderr.string).to match(/ERROR: The data in your request was invalid/) + expect(stderr.string).to match(/Response: y u search wrong/) + end + + it "formats 404s nicely" do + response = Net::HTTPNotFound.new("1.1", "404", "Not Found") + response.instance_variable_set(:@read, true) # I hate you, net/http. + allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "nothing to see here")) + allow(knife).to receive(:run).and_raise(Net::HTTPClientException.new("404 Not Found", response)) + knife.run_with_pretty_exceptions + expect(stderr.string).to match(/ERROR: The object you are looking for could not be found/) + expect(stderr.string).to match(/Response: nothing to see here/) + end + + it "formats 406s (non-supported API version error) nicely" do + response = Net::HTTPNotAcceptable.new("1.1", "406", "Not Acceptable") + response.instance_variable_set(:@read, true) # I hate you, net/http. + + # set the header + response["x-ops-server-api-version"] = Chef::JSONCompat.to_json(min_version: "0", max_version: "1", request_version: "10000000") + + allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "sad trombone")) + allow(knife).to receive(:run).and_raise(Net::HTTPClientException.new("406 Not Acceptable", response)) + + knife.run_with_pretty_exceptions + expect(stderr.string).to match(/The request that .* sent was using API version 10000000./) + expect(stderr.string).to match(/The server you sent the request to supports a min API version of 0 and a max API version of 1./) + expect(stderr.string).to match(/Please either update your .* or the server to be a compatible set./) + end + + it "formats 500s nicely" do + response = Net::HTTPInternalServerError.new("1.1", "500", "Internal Server Error") + response.instance_variable_set(:@read, true) # I hate you, net/http. + allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "sad trombone")) + allow(knife).to receive(:run).and_raise(Net::HTTPFatalError.new("500 Internal Server Error", response)) + knife.run_with_pretty_exceptions + expect(stderr.string).to match(/ERROR: internal server error/) + expect(stderr.string).to match(/Response: sad trombone/) + end + + it "formats 502s nicely" do + response = Net::HTTPBadGateway.new("1.1", "502", "Bad Gateway") + response.instance_variable_set(:@read, true) # I hate you, net/http. + allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "sadder trombone")) + allow(knife).to receive(:run).and_raise(Net::HTTPFatalError.new("502 Bad Gateway", response)) + knife.run_with_pretty_exceptions + expect(stderr.string).to match(/ERROR: bad gateway/) + expect(stderr.string).to match(/Response: sadder trombone/) + end + + it "formats 503s nicely" do + response = Net::HTTPServiceUnavailable.new("1.1", "503", "Service Unavailable") + response.instance_variable_set(:@read, true) # I hate you, net/http. + allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "saddest trombone")) + allow(knife).to receive(:run).and_raise(Net::HTTPFatalError.new("503 Service Unavailable", response)) + knife.run_with_pretty_exceptions + expect(stderr.string).to match(/ERROR: Service temporarily unavailable/) + expect(stderr.string).to match(/Response: saddest trombone/) + end + + it "formats other HTTP errors nicely" do + response = Net::HTTPPaymentRequired.new("1.1", "402", "Payment Required") + response.instance_variable_set(:@read, true) # I hate you, net/http. + allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "nobugfixtillyoubuy")) + allow(knife).to receive(:run).and_raise(Net::HTTPClientException.new("402 Payment Required", response)) + knife.run_with_pretty_exceptions + expect(stderr.string).to match(/ERROR: Payment Required/) + expect(stderr.string).to match(/Response: nobugfixtillyoubuy/) + end + + it "formats NameError and NoMethodError nicely" do + allow(knife).to receive(:run).and_raise(NameError.new("Undefined constant FUUU")) + knife.run_with_pretty_exceptions + expect(stderr.string).to match(/ERROR: .* encountered an unexpected error/) + expect(stderr.string).to match(/This may be a bug in the 'knife' .* command or plugin/) + expect(stderr.string).to match(/Exception: NameError: Undefined constant FUUU/) + end + + it "formats missing private key errors nicely" do + allow(knife).to receive(:run).and_raise(Chef::Exceptions::PrivateKeyMissing.new("key not there")) + allow(knife).to receive(:api_key).and_return("/home/root/.chef/no-key-here.pem") + knife.run_with_pretty_exceptions + expect(stderr.string).to match(%r{ERROR: Your private key could not be loaded from /home/root/.chef/no-key-here.pem}) + expect(stderr.string).to match(/Check your configuration file and ensure that your private key is readable/) + end + + it "formats connection refused errors nicely" do + allow(knife).to receive(:run).and_raise(Errno::ECONNREFUSED.new("y u no shut up")) + knife.run_with_pretty_exceptions + # Errno::ECONNREFUSED message differs by platform + # *nix = Errno::ECONNREFUSED: Connection refused + # win32: Errno::ECONNREFUSED: No connection could be made because the target machine actively refused it. + expect(stderr.string).to match(/ERROR: Network Error: .* - y u no shut up/) + expect(stderr.string).to match(/Check your .* configuration and network settings/) + end + + it "formats SSL errors nicely and suggests to use `knife ssl check` and `knife ssl fetch`" do + error = OpenSSL::SSL::SSLError.new("SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed") + allow(knife).to receive(:run).and_raise(error) + + knife.run_with_pretty_exceptions + + expected_message = <<~MSG + ERROR: Could not establish a secure connection to the server. + Use `.* ssl check` to troubleshoot your SSL configuration. + If your server uses a self-signed certificate, you can use + `.* ssl fetch` to make .* trust the server's certificates. + MSG + expect(stderr.string).to match(expected_message) + end + + end + +end |