summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsersut <serdar@opscode.com>2014-03-30 13:45:00 -0700
committersersut <serdar@opscode.com>2014-03-30 13:45:00 -0700
commit7a1778fb309423114462d578e01ba0e00108010f (patch)
tree792e1fbafdca12725edf923cdad22536f3124fc3
parent0d097217dda26ac5551d1ad24132d9e53a62e0fb (diff)
parentcacf2a53a3b789829dd6b5b2956e07cc1aa42931 (diff)
downloadchef-11.12.0.rc.0.tar.gz
Merge branch 'master' into 11-stable11.12.0.rc.0
Merging mater branch for RC version.
-rw-r--r--CHANGELOG.md72
-rw-r--r--CHEF_MVPS.md84
-rw-r--r--CONTRIBUTING.md12
-rw-r--r--CONTRIBUTIONS.md40
-rw-r--r--DOC_CHANGES.md287
-rw-r--r--Gemfile2
-rw-r--r--README.md2
-rw-r--r--RELEASE_NOTES.md195
-rw-r--r--chef.gemspec13
-rw-r--r--lib/chef/api_client.rb4
-rw-r--r--lib/chef/api_client/registration.rb55
-rw-r--r--lib/chef/application.rb3
-rw-r--r--lib/chef/application/client.rb61
-rw-r--r--lib/chef/client.rb67
-rw-r--r--lib/chef/config.rb11
-rw-r--r--lib/chef/cookbook/chefignore.rb12
-rw-r--r--lib/chef/cookbook/metadata.rb34
-rw-r--r--lib/chef/cookbook/synchronizer.rb4
-rw-r--r--lib/chef/cookbook/syntax_check.rb121
-rw-r--r--lib/chef/dsl/reboot_pending.rb61
-rw-r--r--lib/chef/encrypted_data_bag_item.rb38
-rw-r--r--lib/chef/exceptions.rb14
-rw-r--r--lib/chef/formatters/error_descriptor.rb2
-rw-r--r--lib/chef/guard_interpreter/default_guard_interpreter.rb42
-rw-r--r--lib/chef/guard_interpreter/resource_guard_interpreter.rb122
-rw-r--r--lib/chef/http.rb1
-rw-r--r--lib/chef/http/decompressor.rb11
-rw-r--r--lib/chef/http/remote_request_id.rb46
-rw-r--r--lib/chef/http/simple.rb5
-rw-r--r--lib/chef/http/validate_content_length.rb40
-rw-r--r--lib/chef/knife.rb1
-rw-r--r--lib/chef/knife/bootstrap.rb2
-rw-r--r--lib/chef/knife/bootstrap/README.md12
-rw-r--r--lib/chef/knife/bootstrap/chef-full.erb3
-rw-r--r--lib/chef/knife/client_bulk_delete.rb57
-rw-r--r--lib/chef/knife/client_create.rb6
-rw-r--r--lib/chef/knife/client_delete.rb16
-rw-r--r--lib/chef/knife/cookbook_bulk_delete.rb2
-rw-r--r--lib/chef/knife/cookbook_upload.rb24
-rw-r--r--lib/chef/knife/core/bootstrap_context.rb2
-rw-r--r--lib/chef/knife/core/ui.rb47
-rw-r--r--lib/chef/knife/node_run_list_add.rb33
-rw-r--r--lib/chef/knife/raw.rb1
-rw-r--r--lib/chef/knife/ssh.rb75
-rw-r--r--lib/chef/knife/ssl_check.rb213
-rw-r--r--lib/chef/knife/ssl_fetch.rb145
-rw-r--r--lib/chef/mixin/deep_merge.rb18
-rw-r--r--lib/chef/mixin/shell_out.rb12
-rw-r--r--lib/chef/node.rb29
-rw-r--r--lib/chef/node/attribute_collections.rb9
-rw-r--r--lib/chef/node/immutable_collections.rb41
-rw-r--r--lib/chef/platform/provider_mapping.rb39
-rw-r--r--lib/chef/platform/query_helpers.rb12
-rw-r--r--lib/chef/policy_builder/expand_node_object.rb9
-rw-r--r--lib/chef/provider/cron.rb28
-rw-r--r--lib/chef/provider/deploy.rb2
-rw-r--r--lib/chef/provider/group.rb2
-rw-r--r--lib/chef/provider/ifconfig/debian.rb27
-rw-r--r--lib/chef/provider/mount/mount.rb2
-rw-r--r--lib/chef/provider/ohai.rb11
-rw-r--r--lib/chef/provider/package/dpkg.rb3
-rw-r--r--lib/chef/provider/package/windows.rb80
-rw-r--r--lib/chef/provider/package/windows/msi.rb69
-rw-r--r--lib/chef/provider/powershell_script.rb25
-rw-r--r--lib/chef/provider/service/macosx.rb82
-rw-r--r--lib/chef/provider/service/solaris.rb18
-rw-r--r--lib/chef/recipe.rb2
-rw-r--r--lib/chef/request_id.rb37
-rw-r--r--lib/chef/resource.rb25
-rw-r--r--lib/chef/resource/conditional.rb27
-rw-r--r--lib/chef/resource/cron.rb20
-rw-r--r--lib/chef/resource/execute.rb2
-rw-r--r--lib/chef/resource/powershell_script.rb24
-rw-r--r--lib/chef/resource/script.rb25
-rw-r--r--lib/chef/resource/subversion.rb4
-rw-r--r--lib/chef/resource/windows_package.rb79
-rw-r--r--lib/chef/resource/windows_script.rb5
-rw-r--r--lib/chef/resource_reporter.rb11
-rw-r--r--lib/chef/resources.rb1
-rw-r--r--lib/chef/rest.rb13
-rw-r--r--lib/chef/run_context.rb24
-rw-r--r--lib/chef/run_context/cookbook_compiler.rb12
-rw-r--r--lib/chef/run_status.rb5
-rw-r--r--lib/chef/server_api.rb4
-rw-r--r--lib/chef/util/editor.rb92
-rw-r--r--lib/chef/util/file_edit.rb76
-rw-r--r--lib/chef/version.rb2
-rw-r--r--lib/chef/win32/api/installer.rb166
-rw-r--r--lib/chef/win32/version.rb8
-rw-r--r--spec/data/standalone_cookbook/Gemfile1
-rw-r--r--spec/data/standalone_cookbook/chefignore9
-rw-r--r--spec/data/standalone_cookbook/recipes/default.rb3
-rw-r--r--spec/data/standalone_cookbook/vendor/bundle/ruby/2.0.0/gems/multi_json-1.9.0/lib/multi_json.rb1
-rw-r--r--spec/functional/dsl/reboot_pending_spec.rb118
-rw-r--r--spec/functional/resource/base.rb4
-rw-r--r--spec/functional/resource/deploy_revision_spec.rb193
-rw-r--r--spec/functional/resource/git_spec.rb2
-rw-r--r--spec/functional/resource/ohai_spec.rb65
-rw-r--r--spec/functional/resource/powershell_spec.rb263
-rw-r--r--spec/functional/resource/registry_spec.rb9
-rw-r--r--spec/functional/win32/versions_spec.rb6
-rw-r--r--spec/integration/client/client_spec.rb14
-rw-r--r--spec/integration/knife/chefignore_spec.rb3
-rw-r--r--spec/integration/knife/raw_spec.rb21
-rw-r--r--spec/integration/knife/redirection_spec.rb20
-rw-r--r--spec/integration/solo/solo_spec.rb19
-rw-r--r--spec/spec_helper.rb3
-rw-r--r--spec/support/shared/functional/windows_script.rb5
-rw-r--r--spec/support/shared/integration/app_server_support.rb42
-rw-r--r--spec/support/shared/integration/integration_helper.rb1
-rw-r--r--spec/support/shared/unit/script_resource.rb38
-rw-r--r--spec/unit/api_client/registration_spec.rb147
-rw-r--r--spec/unit/api_client_spec.rb46
-rw-r--r--spec/unit/application/client_spec.rb49
-rw-r--r--spec/unit/client_spec.rb574
-rw-r--r--spec/unit/cookbook/chefignore_spec.rb10
-rw-r--r--spec/unit/cookbook/metadata_spec.rb46
-rw-r--r--spec/unit/cookbook/syntax_check_spec.rb29
-rw-r--r--spec/unit/cookbook_spec.rb10
-rw-r--r--spec/unit/dsl/reboot_pending_spec.rb100
-rw-r--r--spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb56
-rw-r--r--spec/unit/http/simple_spec.rb32
-rw-r--r--spec/unit/http/validate_content_length_spec.rb187
-rw-r--r--spec/unit/knife/bootstrap_spec.rb17
-rw-r--r--spec/unit/knife/client_bulk_delete_spec.rb161
-rw-r--r--spec/unit/knife/client_create_spec.rb30
-rw-r--r--spec/unit/knife/client_delete_spec.rb45
-rw-r--r--spec/unit/knife/cookbook_upload_spec.rb269
-rw-r--r--spec/unit/knife/core/bootstrap_context_spec.rb12
-rw-r--r--spec/unit/knife/core/ui_spec.rb147
-rw-r--r--spec/unit/knife/node_run_list_add_spec.rb25
-rw-r--r--spec/unit/knife/ssh_spec.rb23
-rw-r--r--spec/unit/knife/ssl_check_spec.rb187
-rw-r--r--spec/unit/knife/ssl_fetch_spec.rb151
-rw-r--r--spec/unit/knife_spec.rb55
-rw-r--r--spec/unit/mixin/deep_merge_spec.rb17
-rw-r--r--spec/unit/node/attribute_spec.rb7
-rw-r--r--spec/unit/node/immutable_collections_spec.rb61
-rw-r--r--spec/unit/node_spec.rb9
-rw-r--r--spec/unit/platform/query_helpers_spec.rb32
-rw-r--r--spec/unit/platform_spec.rb368
-rw-r--r--spec/unit/policy_builder/expand_node_object_spec.rb2
-rw-r--r--spec/unit/provider/cron_spec.rb176
-rw-r--r--spec/unit/provider/group_spec.rb5
-rw-r--r--spec/unit/provider/ifconfig/debian_spec.rb275
-rw-r--r--spec/unit/provider/mount/mount_spec.rb25
-rw-r--r--spec/unit/provider/ohai_spec.rb5
-rw-r--r--spec/unit/provider/package/dpkg_spec.rb4
-rw-r--r--spec/unit/provider/package/windows/msi_spec.rb60
-rw-r--r--spec/unit/provider/package/windows_spec.rb80
-rw-r--r--spec/unit/provider/service/macosx_spec.rb40
-rw-r--r--spec/unit/provider/service/solaris_smf_service_spec.rb45
-rw-r--r--spec/unit/recipe_spec.rb4
-rw-r--r--spec/unit/resource/conditional_spec.rb25
-rw-r--r--spec/unit/resource/cron_spec.rb9
-rw-r--r--spec/unit/resource/powershell_spec.rb87
-rw-r--r--spec/unit/resource/subversion_spec.rb5
-rw-r--r--spec/unit/resource/windows_package_spec.rb74
-rw-r--r--spec/unit/resource_reporter_spec.rb2
-rw-r--r--spec/unit/resource_spec.rb24
-rw-r--r--spec/unit/rest_spec.rb66
-rw-r--r--spec/unit/run_context/cookbook_compiler_spec.rb12
-rw-r--r--spec/unit/run_context_spec.rb7
-rw-r--r--spec/unit/util/editor_spec.rb152
-rw-r--r--spec/unit/util/file_edit_spec.rb38
165 files changed, 7152 insertions, 1094 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000000..657e1f6adf
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,72 @@
+# Chef Client Changelog
+
+## Unreleased
+
+* Including a recipe from a cookbook not in the dependency graph logs
+ a MissingCookbookDependency warning. Fixes CHEF-4367.
+* Improves syntax check speed for Ruby 1.9+, especially when using bundler.
+* Send X-Remote-Request-Id header in order to be able to correlate actions during a single run.
+* Fix for CHEF-5048.
+* Fix for CHEF-5052.
+* Fix for CHEF-5018.
+* Add --validator option to `knife client create` to be able to create validator clients via knife.
+* Add --delete-validators option to `knife client delete` in order to prevent accidental deletion of validator clients.
+* Add --delete-validators option to `knife client bulk delete` in order to prevent accidental deletion of validator clients.
+* Add -r / --runlist option to chef-client which permanently sets or changes the run_list of a node.
+* CHEF-5030: clean up debian ifconfig provider code
+* CHEF-5001: spec tests for multiple rollbacks
+* Added ohai7 'machinename' attribute as source of `node_name` information
+* CHEF-4773: add ruby-shadow support to Mac and FreeBSD distros
+* Service Provider for MacOSX now supports `enable` and `disable`
+* CHEF-5086: Add reboot_pending? helper to DSL
+* Upgrade ohai to 7.0.0.rc.0
+* Make the initial bootstrap message more user friendly (CHEF-5102)
+* Correctly handle exceptions in formatters when exception.message is nil (CHEF-4743)
+* Fix convergence message in deploy provider (CHEF-4929)
+* Make group resource idempotent when gid is specified as a string. (CHEF-4927)
+* Non-dupable elements are now handled when duping attribute arrays. (CHEF-4799)
+* ruby-shadow is not installed on cygwin platform anymore. (CHEF-4946)
+* Upgrade chef-zero to 2.0, remove native-compiled puma as chef dependency. (CHEF-4901/CHEF-5005)
+* Don't honor splay when sent USR1 signal.
+* Don't set log_level in client.rb by default (CHEF-3698)
+* Add IBM PowerKVM to Platform map. (CHEF-5135)
+* Cookbook metadata now allows boolean and numeric attributes. (CHEF-4075)
+* Knife ssh uses cloud port attribute when available. (CHEF-4962)
+* Client info and debug logs now contain cookbook versions in addition to cookbook names. (CHEF-4643)
+* ShellOut mixin now exposes a method to capture the live stream during command execution. (CHEF-5017)
+* Service provider is now aware of maintenance state on Solaris. (CHEF-4990)
+* Refactor Chef::Util::FileEdit to indicate the purpose of the former file_edited, now unwritten_changes?. (CHEF-3714)
+* Fixed FileEdit#insert_line_if_no_match to match multiple times. (CHEF-4173)
+* Hide passwords in error messages from the Subversion resource. (CHEF-4680)
+* The dpkg package provider now supports epoch versions. (CHEF-1752)
+* Multiple missing dependencies are now listed on knife cookbook upload. (CHEF-4851)
+* Add a public file_edited? method to Chef::Util::FileEdit. (CHEF-3714)
+* Package provider defaults to IPS provider on Solaris 5.11+ (CHEF-5037)
+* Chef::REST works with frozen options. (CHEF-5064)
+* Service provider now uses Systemd on ArchLinux. (CHEF-4905)
+* Support knife node run_list add --before. (CHEF-3812)
+* Don't destructively merge subhashes in hash_only_merge!. (CHEF-4918)
+* Display correct host name in knife ssh error message (CHEF-5029)
+* Knife::UI#confirm now has a default_choice option. (CHEF-5057)
+* Add knife 'ssl check' and 'ssl fetch' commands for debugging SSL errors. (CHEF-4711)
+* Usermod group provider is only used on OpenSuse. (OHAI-339)
+* Add knife 'ssl check' and 'ssl fetch' commands for debugging SSL errors (CHEF-4711)
+* Cron resource accepts a weekday attribute as a symbol. (CHEF-4848)
+* Cron resource accepts special strings, e.g. @reboot (CHEF-2816)
+* Call WIN32OLE.ole_initialize before using WMI (CHEF-4888)
+* Fix TypeError when calling dup on un-dupable objects in DeepMerge
+* Add optional client-side generation of client keys during registration (CHEF-4373)
+* Restore warning for the overlay feature in `knife cookbook upload`,
+ which was accidentally removed in 11.0.0.
+* Don't save the run_list during `node.save` when running with override run list. (CHEF-4443)
+* Enable Content-Length validation for Chef::HTTP::Simple and fix issues around it. (CHEF-5041, CHEF-5100)
+* Windows MSI Package Provider (CHEF-5087)
+* Fix mount resource when device is a relative symlink (CHEF-4957)
+* Increase bootstrap log_level when knife -V -V is set (CHEF-3610)
+* Knife cookbook test should honor chefignore (CHEF-4203)
+* Fix ImmutableMash and ImmutableArray to_hash and to_a methods (CHEF-5132)
+* guard_interpreter attribute: use powershell\_script, other script resources in guards (CHEF-4553)
+
+## Last Release: 11.10.0 (02/06/2014)
+
+http://docs.opscode.com/release/11-10/release_notes.html
diff --git a/CHEF_MVPS.md b/CHEF_MVPS.md
new file mode 100644
index 0000000000..dfd0985bba
--- /dev/null
+++ b/CHEF_MVPS.md
@@ -0,0 +1,84 @@
+### Chef is proud of our community!
+
+Every release of Chef we pick someone from the community to name as the Most Valuable Player for that release. It could be someone who provided a big feature, reported a security vulnerability, or someone doing great things in the community that we want to highlight.
+
+#### Hall of Fame
+
+After receiving three MVP awards, we add someone to the hall of fame. We want to express our gratitude to their continuing participation and give newer community members the opportunity to be reconignized.
+
+* Matthew Kent
+* Doug MacEachern
+* Tollef Fog Heen
+* Thom May
+* Bryan Berry
+* Bryan McLellan
+
+#### The MVP recipients
+
+| Release | Date | MVP |
+|---------|------|-----|
+| [Client 11.10.4](http://www.getchef.com/blog/2014/02/20/chef-client-patch-release-11-10-4/) | 2014-02-20 | Jon Cowie |
+| [Client 11.10.2](http://www.getchef.com/blog/2014/02/18/chef-client-release-11-10-2-10-30-4/) | 2014-02-18 | Eric Tucker |
+| [Client 11.10.0](http://www.getchef.com/blog/2014/02/06/chef-client-11-10-0-release/) | 2014-02-06 | Nikhil Benesch |
+| [Client 11.8.2](http://www.getchef.com/blog/2013/12/06/release-chef-client-10-30-2-11-8-2-mixlib-shellout-1-3-0/) | 2013-12-06 | James Ogden |
+| [Client 11.8.0](http://www.opscode.com/blog/2013/10/31/release-chef-client-11-8-0-ohai-6-20-0/) | 2013-10-31 | Eric Saxby |
+| [Client 11.6.2](http://www.getchef.com/blog/2013/10/08/release-chef-client-11-6-2-10-28-2/) | 2013-10-08 | Jeff Blaine |
+| [Client 11.6.0](http://www.opscode.com/blog/2013/07/23/chef-client-11-6-0-ohai-6-18-0-and-more/) | 2013-07-23 | Jesse Campbell |
+| [Client 11.4.0](http://www.opscode.com/blog/2013/02/13/chef-client-11-4-0-10-22-0-released/) | 2013-02-13 | Vaidas Jablonskis |
+| [Client 11.2.0](http://www.opscode.com/blog/2013/02/07/chef-client-11-2-0-10-20-0-released/) | 2013-02-06 | Mike Javorski |
+| [Chef 11.0.0](http://www.opscode.com/blog/2013/02/04/chef-11-released/) | 2013-02-04 | Andrea Campi, Bryan Berry |
+| [Chef 10.30.4](http://www.getchef.com/blog/2014/02/18/chef-client-release-11-10-2-10-30-4/) | 2014-02-18 | Christopher Laco |
+| [Chef 10.30.2](http://www.getchef.com/blog/2013/12/06/release-chef-client-10-30-2-11-8-2-mixlib-shellout-1-3-0/) | 2013-12-06 | Phil Dibowitz |
+| [Chef 10.28.2](http://www.getchef.com/blog/2013/10/08/release-chef-client-11-6-2-10-28-2/) | 2013-10-08 | Jeff Blaine |
+| [Chef 10.28.0](http://www.opscode.com/blog/2013/09/03/chef-10-28-0-released/) | 2013-09-03 | Jeff Blaine |
+| [Chef 10.26.0](http://www.opscode.com/blog/2013/05/08/chef-10-26-0-released/) | 2013-05-08 | Ranjib Dey |
+| [Chef 10.24.0](http://www.opscode.com/blog/2013/02/15/chef-server-11-0-6-and-10-24-0-released/) | 2013-02-15 | Anthony Goddard |
+| [Chef 10.22.0](http://www.opscode.com/blog/2013/02/13/chef-client-11-4-0-10-22-0-released/) | 2013-02-13 | Brian Bianco |
+| [Chef 10.20.0](http://www.opscode.com/blog/2013/02/07/chef-client-11-2-0-10-20-0-released/) | 2013-02-06 | Chris Roberts |
+| [Chef 10.18.2](http://www.opscode.com/blog/2013/01/18/chef-10-18-2-bugfix-release/) | 2013-01-18 | Fletcher Nichol |
+| [Chef 10.18.0](http://www.opscode.com/blog/2013/01/16/chef-10-18-0-released/) | 2013-01-16 | Xabier de Zuazo |
+| [Chef 10.16.6](http://www.opscode.com/blog/2013/01/11/chef-10-16-6-security-release/) | 2013-01-11 | Dan Kubb |
+| [Chef 10.16.4](http://www.opscode.com/blog/2012/12/26/chef-10-16-4-released/) | 2012-12-26 | Avishai Ish-Shalom |
+| [Chef 10.16.2](http://www.opscode.com/blog/2012/10/26/chef-10-16-2-released/) | 2012-10-26 | Jamie Winsor |
+| [Chef 10.16.0](http://www.opscode.com/blog/2012/10/22/chef-10-16-0-released/) | 2012-10-22 | John Dewey |
+| [Chef 10.14.4](http://www.opscode.com/blog/2012/09/28/chef-10-14-4-released/) | 2012-09-27 | Kendrick Martin |
+| [Chef 10.14.2](http://www.opscode.com/blog/2012/09/11/chef-10-14-2-released/) | 2012-09-10 | Phil Dibowitz, Tim Smith |
+| [Chef 10.14.0](http://www.opscode.com/blog/2012/09/07/chef-10-14-0-released/) | 2012-09-07 | Xabier de Zuazo |
+| [Chef 10.12.0](http://www.opscode.com/blog/2012/06/19/chef-10-12-0-released/) | 2012-06-18 | Chris Roberts |
+| [Chef 0.10.10](http://www.opscode.com/blog/2012/05/11/chef-0-10-10-released/) | 2012-05-11 | Juanje Ojeda, Igor Afonov |
+| [Chef 0.10.8](http://www.opscode.com/blog/2011/12/15/chef-0-10-8-released/) | 2011-12-15 | Bryan Berry |
+| [Chef 0.10.6](http://www.opscode.com/blog/2011/12/14/chef-0-10-6-released/) | 2011-12-13 | Andrea Campi |
+| [Chef 0.10.4](http://www.opscode.com/blog/2011/08/11/chef-0-10-4-released/) | 2011-08-11 | Matthew Kent |
+| [Chef 0.10.2](http://www.opscode.com/blog/2011/06/29/chef-0-10-2-and-0-9-18-released/) | 2011-06-29 | Daniel Oliver |
+| [Chef 0.10.0](http://www.opscode.com/blog/2011/05/02/chef-0-10-0-released/) | 2011-05-02 | Grace Mollison, Darrin Eden |
+| [Chef 0.9.18](http://www.opscode.com/blog/2011/06/29/chef-0-10-2-and-0-9-18-released/) | 2011-06-29 | Jesai Langenbach |
+| [Chef 0.9.16](http://www.opscode.com/blog/2011/04/15/chef-0-9-16-released/) | 2011-04-15 | Michael Leinartas |
+| [Chef 0.9.14](http://www.opscode.com/blog/2011/03/04/chef-0-9-14-released/) | 2011-03-04 | Gilles Devaux |
+| [Chef 0.9.12](http://www.opscode.com/blog/2010/10/22/chef-0-9-12-released/) | 2010-10-22 | Laurent Désarmes |
+| [Chef 0.9.10](http://www.opscode.com/blog/2010/10/19/chef-0-9-10-ohai-0-5-8-and-mixliblog-1-2-0-released/) | 2010-10-19 | Toomas Pelberg, Tommy Bishop |
+| [Chef 0.9.8](http://www.opscode.com/blog/2010/08/05/chef-0-9-8-and-mixlib-authentication-1-1-4-released/) | 2010-08-05 | Joe Williams |
+| [Chef 0.9.6](http://www.opscode.com/blog/2010/07/03/chef-0-9-6-released/) | 2010-07-03 | Caleb Tennis |
+| [Chef 0.9.4](http://www.opscode.com/blog/2010/06/30/chef-0-9-4-released/) | 2010-06-30 | Ian Meyer |
+| [Chef 0.9.0](http://www.opscode.com/blog/2010/06/21/chef-0-9-0-and-ohai-0-5-6-released/) | 2010-06-21 | Doug MacEachern |
+| [Chef 0.8.16](http://www.opscode.com/blog/2010/05/11/chef-0-8-16-and-ohai-0-5-4-release/) | 2010-05-11 | Akzhan Abdulin |
+| [Chef 0.8.14](http://www.opscode.com/blog/2010/05/07/chef-0-8-14-release/) | 2010-05-07 | Renaud Chaput |
+| [Chef 0.8.10](http://www.opscode.com/blog/2010/04/02/chef-0-8-10-release/) | 2010-04-02 | Thom May, Tollef Fog Heen |
+| [Chef 0.8.8](http://www.opscode.com/blog/2010/03/18/chef-0-8-8-release/) | 2010-03-18 | Eric Hankins |
+| [Chef 0.8.6](http://www.opscode.com/blog/2010/03/05/chef-0-8-6-release/) | 2010-03-05 | Ian Meyer |
+| [Chef 0.8.4](http://www.opscode.com/blog/2010/03/02/chef-0-8-4-release/) | 2010-03-02 | Tollef Fog Heen |
+| [Chef 0.8.2](http://www.opscode.com/blog/2010/03/01/chef-0-8-2-release/) | 2010-03-01 | Scott M. Likens |
+| [Chef 0.7.16](http://www.opscode.com/blog/2009/12/22/chef-0-7-16-release/) | 2009-12-22 | Bryan McLellan |
+| [Chef 0.7.14](http://www.opscode.com/blog/2009/10/26/chef-0-7-14-ohai-0-3-6-releases/) | 2009-10-16 | Thom May |
+| [Chef 0.7.12](http://www.opscode.com/blog/2009/10/06/chef-0-7-12rc0-ohai-0-3-4rc0-releases/) | 2009-10-06 | Diego Algorta |
+| [Chef 0.7.10](http://www.opscode.com/blog/2009/09/04/chef-0-7-10-release/) | 2009-09-04 | Dan DeLeo |
+| [Chef 0.7.8](http://www.opscode.com/blog/2009/08/13/chef-0-7-8-release/) | 2009-08-13 | Jeppe Nejsum Madsen |
+| [Chef 0.7.6](http://www.opscode.com/blog/2009/08/08/chef-0-7-6-release/) | 2009-08-08 | Grant Zanetti |
+| [Chef 0.7.4](http://www.opscode.com/blog/2009/06/26/back-to-back-chef-0-7-2-and-chef-0-7-4-released/) | 2009-06-26 | Hongli Lai |
+| [Chef 0.7.2](http://www.opscode.com/blog/2009/06/26/back-to-back-chef-0-7-2-and-chef-0-7-4-released/) | 2009-06-26 | Joshua Sierles |
+| [Chef 0.7.0](http://www.opscode.com/blog/2009/06/10/chef-0-7-0-release/) | 2009-06-10 | Matthew Kent |
+| [Chef 0.6.2](http://www.opscode.com/blog/2009/04/29/chef-0-6-2-release/) | 2009-04-29 | David Balatero |
+| [Chef 0.6.0](http://www.opscode.com/blog/2009/04/29/chef-0-6-0-release/) | 2009-04-29 | Matthew Kent |
+| [Chef 0.5.6](http://www.opscode.com/blog/2009/03/06/chef-0-5-6/) | 2009-03-06 | Sean Cribbs |
+| [Chef 0.5.4](http://www.opscode.com/blog/2009/02/13/chef-0-5-4/) | 2009-02-13 | Arjuna Christensen |
+| [Chef 0.5.2](http://www.opscode.com/blog/2009/02/01/chef-0-5-2-and-ohai-0-1-4/) | 2009-02-01 | Bryan McLellan |
+
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 523fadc45f..9d9839fd5c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -24,7 +24,7 @@ Chef uses the Apache 2.0 license to strike a balance between open contribution a
The license tells you what rights you have that are provided by the copyright holder. It is important that the contributor fully understands what rights
they are licensing and agrees to them. Sometimes the copyright holder isn't the contributor, most often when the contributor is doing work for a company.
-To make a good faith effort to ensure these criteria are met, Opscode requires a Contributor License Agreement (CLA) or a Corporate Contributor License
+To make a good faith effort to ensure these criteria are met, Chef requires a Contributor License Agreement (CLA) or a Corporate Contributor License
Agreement (CCLA) for all contributions. This is without exception due to some matters not being related to copyright and to avoid having to continually
check with our lawyers about small patches.
@@ -74,7 +74,7 @@ helpful to be clear about your use case and change so they can understand it eve
### Github and Pull Requests
-All of Opscode's open source projects are available on [Github](http://www.github.com/opscode).
+All of Chef's open source projects are available on [Github](http://www.github.com/opscode).
We don't require you to use Github, and we will even take patch diffs attached to tickets on the tracker.
However Github has a lot of convenient features, such as being able to see a diff of changes between a
@@ -115,7 +115,7 @@ and accounting for it.
## Code Review
-Opscode regularly reviews code contributions and provides suggestions for improvement in the code itself or the implementation.
+Chef regularly reviews code contributions and provides suggestions for improvement in the code itself or the implementation.
We find contributions by searching the ticket tracker for _resolved_ tickets with a status of _fixed_. If we have feedback we will
reopen the ticket and you should resolve it again when you've made the changes or have a response to our feedback. When we believe
@@ -134,14 +134,14 @@ The versioning for the Chef project is X.Y.Z.
* Y is a minor release, which adds both new features and bug fixes
* Z is a patch release, which adds just bug fixes
-Major releases and have historically been once a year. Minor releases for Chef average every two months and patch releases come as needed.
+Major releases have historically been once a year. Minor releases for Chef average every three months and patch releases come as needed.
There are usually beta releases and release candidates (RC) of major and minor releases announced on
the [chef-dev mailing list](http://lists.opscode.com/sympa/info/chef-dev). Once an RC is released, we wait at least three
days to allow for testing for regressions before the final release. If a blocking regression is found then another RC is made containing
the fix and the timer is reset.
-Once the official release is made, the release notes are available on the [Opscode blog](http://www.opscode.com/blog).
+Once the official release is made, the release notes are available on the [Chef blog](http://www.getchef.com/blog).
## Working with the community
@@ -151,5 +151,5 @@ These resources will help you learn more about Chef and connect to other members
* #chef and #chef-hacking IRC channels on irc.freenode.net
* [Community Cookbook site](http://community.opscode.com)
* [Chef wiki](http://wiki.opscode.com/display/chef)
-* Opscode Chef [product page](http://www.opscode.com/chef)
+* Chef [product page](http://www.getchef.com/chef)
diff --git a/CONTRIBUTIONS.md b/CONTRIBUTIONS.md
new file mode 100644
index 0000000000..9daf7afb40
--- /dev/null
+++ b/CONTRIBUTIONS.md
@@ -0,0 +1,40 @@
+<!---
+This file is reset every time a new release is done. The contents of this file are for the currently unreleased version.
+
+Example Contribution:
+* **kalistec**: Improved file resource greatly.
+-->
+# Chef Client Contributions:
+
+* **jonlives**: Changed the order of recipe and cookbook name setting. Fixes CHEF-5052.
+* **jaymzh**: Added support for `enable` and `disable` to MacOSX service provider.
+* **bossmc**: Made formatters more resilient to nil exception messages.
+* **valodzka**: Fixed the convergence message in deploy provider.
+* **linkfanel**: Made attribute arrays able to handle non-dupable elements while being duped.
+* **linkfanel**: Removed ruby-shadow installation on cygwin platform.
+* **lbragstad**: Add IBM PowerKVM to platform map.
+* **slantview**: Allow boolean and numerics in cookbook metadata.
+* **jeffmendoza**: Made knife to use cloud attribute for port when available.
+* **ryotarai**: Added a method to capture IO for live stream.
+* **sawanoboly**: Fixed service provider to be aware of maintenance state on Solaris.
+* **cbandy**: Refactored Chef::Util::FileEdit.
+* **cbandy**: Fixed insert_line_if_no_match to run multiple times.
+* **pavelbrylov**: Modified subversion resource to hide password from error messages.
+* **eherot**: Add support for epoch versions to the dpkg package provider.
+* **jdmurphy**: Display all missing dependencies when uploading cookbooks.
+* **nkrinner**: Add a public file_edited? method to Chef::Util::FileEdit.
+* **ccope**: Made package provider to use IPS provider in Solaris 5.11+
+* **josephholsten**: Changed Chef::REST to be able to handle frozen options.
+* **andreasrs**: Changed service provider to use Systemd on ArchLinux.
+* **eherot**: Add support for epoch versions to the dpkg package provider.
+* **jdmurphy**: Display all missing dependencies when uploading cookbooks.
+* **nkrinner**: Add a public file_edited? method to Chef::Util::FileEdit.
+* **jjasghar**: Output correct host name in knife ssh error message.
+* **esigler**: Added default_choice option to Knife::UI#confirm.
+* **DracoAter**: Add support to the Cron resource for special strings, e.g. @reboot.
+* **ryotarai**: Add support to the Cron resource for weekday passed as a symbol.
+* **thommay **: Made sure that `node.save` doesn't save the run_list when chef is running with override-run-list.
+* **Maxime Caumartin**: Fix mount resource when device is a relative symlink.
+* **jessehu**: Increase bootstrap log_level when knife -V -V is set
+* **mveitas**: knife cookbook test honors chefignore
+* **zuazo**: Fix ImmutableMash and ImmutableArray to_hash and to_a methods
diff --git a/DOC_CHANGES.md b/DOC_CHANGES.md
new file mode 100644
index 0000000000..f29d6ab132
--- /dev/null
+++ b/DOC_CHANGES.md
@@ -0,0 +1,287 @@
+<!---
+This file is reset every time a new release is done. This file describes changes that have not yet been released.
+
+Example Doc Change:
+### Headline for the required change
+Description of the required change.
+-->
+
+# Chef Client Doc Changes:
+
+### --validator option for `knife client create`
+Boolean value. If set to true, knife creates a validator client o.w. it creates a user client. Default is false.
+
+### --delete-validators for `knife client delete`
+Option that is required to be specified if user is attempting to delete a validator client. No effect while deleting a user client.
+
+### --delete-validators for `knife client bulk delete`
+Option that is required to be specified if user is attempting to delete a validator client. If not specified users cannot delete a client if it's validator client. If specified knife asks users for confirmation of deleting clients and validator clients seperately. Some examples for scripting:
+
+To delete all non-validator clients:
+`knife client bulk delete regexp --yes`
+
+To delete all clients including validators:
+`knife client bulk delete regexp --delete-validators --yes`
+
+### -r / --runlist option for chef-client
+Option similar to `-o` which sets or changes the run_list of a node permanently.
+
+### knife bootstrap -V -V
+
+Running ```knife bootstrap -V -V``` will run the initial chef-client with a log level of debug.
+
+### knife cookbook test
+
+```knife cookbook test``` respects chefignore files when selecting which files to test.
+
+### OHAI 7 Upgrade
+Unless there are major issues, 11.12.0 will include OHAI 7. We already have ohai 7 docs in place. We probably need to add some notes to ohai 6 notes that one should now use the newer version when possible.
+
+### New knife command: `knife ssl check [URI]`
+
+The `knife ssl check` command is used to check or troubleshoot SSL
+configuration. When run without arguments, it tests whether chef/knife
+can verify the Chef server's SSL certificate. Otherwise it connects to
+the server specified by the given URL.
+
+Examples:
+
+* Check knife's configuration against the chef-server: `knife ssl check`
+* Check chef-client's configuration: `knife ssl check -c /etc/chef/client.rb`
+* Check whether an external server's SSL certificate can be verified:
+ `knife ssl check https://www.getchef.com`
+
+### New knife command: `knife ssl fetch [URI]`
+
+The `knife ssl fetch` command is used to copy certificates from an HTTPS
+server to the `trusted_certs_dir` of knife or `chef-client`. If the
+certificates match the hostname of the remote server, this command is
+all that is required for knife or chef-client to verify the remote
+server in the future. WARNING: `knife` has no way to determine whether
+the certificates were tampered with in transit. If that happens,
+knife/chef-client will trust potentially forged/malicious certificates
+until they are deleted from the `trusted_certs_dir`. Users are *VERY STRONGLY*
+encouraged to verify the authenticity of the certificates downloaded
+with `knife fetch` by some trustworthy means.
+
+Examples:
+
+* Fetch the chef server's certificates for use with knife:
+ `knife ssl fetch`
+* Fetch the chef server's certificates for use with chef-client:
+ `knife ssl fetch -c /etc/chef/client.rb`
+* Fetch the certificates from an arbitrary server:
+ `knife ssl fetch https://www.getchef.com`
+
+### OpenSUSE and SUSE differentiation
+
+With the recent change in OHAI to differentiate between SUSE (or SLES - SUSE Enterprise Linux Server) and OpenSUSE we need to update our docs to reflect following (quoting btm):
+
+* Platform SUSE should be changed to OpenSUSE everywhere that it previously meant OpenSUSE but said SUSE.
+* Keeping SLES as platform SUSE is still a bit confusing, but that's the least horrible path we chose.
+* It's all still very confusing. :)
+
+This page is an example but we probably want to search for `suse` in our doc repo and see if there is anywhere else.
+
+http://docs.opscode.com/dsl_recipe_method_platform_family.html
+
+### Cron Resource
+
+The weekday attribute now accepts the weekday as a symbol, e.g. :monday or :thursday.
+
+The new time attribute takes special time values specified by cron as a symbol, such as :reboot or :monthly.
+
+### SSL Verification Warnings
+
+Chef 11.12 emits verbose warnings when configured to not verify SSL
+certificates. Though not verifying certificates is currently the default
+setting, this is unsecure and a future release of Chef will change the
+default setting so that SSL certificates are verified.
+
+Users are encouraged to resolve these warnings by adding the following
+to their configuration files (client.rb or solo.rb):
+
+`ssl_verify_mode :verify_peer`
+
+This setting will check that the certificate presented by HTTPS servers
+is signed by a trusted authority. By default, the on-premises Enterprise
+Chef and Open Source Chef server use a self-signed certificate that
+chef-client will not be able to verify, which will result in SSL errors
+when connecting to the server. To check SSL connectivity with the
+server, users can use the `knife ssl check` command. If the server is
+configured to use an untrusted self-signed certificate, users can
+configure chef-client to trust the remote server by copying the server's
+certificate to the `trusted_certs_dir`. The `knife ssl fetch` command
+can be used to automate this process; however, `knife` is not able to
+determine whether certificates downloaded with `knife ssl fetch` have
+been tampered with during the download, so users should verify the
+authenticity of any certificates downloaded this way.
+
+If a user absolutely cannot enable certificate verification and wishes
+to suppress SSL warnings, they can use HTTP instead of HTTPS as a
+workaround. This is highly discouraged. If some behavior of Chef
+prevents a user from enabling SSL certificate verification, they are
+encouraged to file a bug report.
+
+### New Configuration Option: `local_key_generation`
+
+Chef 11.x servers support client-side generation of keys when creating
+new clients. Generating the keys on the client provides two benefits: 1)
+the private key never travels over the network, which improves security;
+2) the CPU load imposed by key creation is moved to the node and
+distributed, which allows the server to handle more concurrent client
+registrations.
+
+For compatibility reasons, this feature is opt-in, but will likely be
+the default or even only behavior in Chef 12.
+
+To enable it, add this to client.rb before running chef-client on a node
+for the first time:
+
+```
+local_key_generation true
+```
+
+The default value of this setting is `false`
+
+*NOTE:* Chef servers that implement the 10.x API do not support this
+feature. Enabling this on a client that connects to a 10.X API server
+will cause client registration to silently fail. Don't do it.
+
+### Windows Installer (MSI) Package Provider
+
+The windows_package provider installs and removes Windows Installer (MSI) packages.
+This provider utilizies the ProductCode extracted from the MSI package to determine
+if the package is currently installed.
+
+You may use the ```package``` resource to use this provider, and you must use the
+```package``` resource if you are also using the windows cookbook, which contains
+the windows_package LWRP.
+
+#### Example
+
+```
+package "7zip" do
+ action :install
+ source 'C:\7z920.msi'
+end
+```
+
+#### Actions
+* :install
+* :remove
+
+#### Attributes
+* source - The location of the package to install. Default value: the ```name``` of the resource.
+* options - Additional options that are passed to msiexec.
+* installer_type - The type of package being installed. Can be auto-detected. Currently only :msi is supported.
+* timeout - The time in seconds allowed for the package to successfully be installed. Defaults to 600 seconds.
+* returns - Return codes that signal a successful installation. Defaults to 0.
+
+### New resource attribute: `guard_interpreter`
+All resources have a new attribute, `guard_interpreter`, which specifies a
+Chef script resource that should be used to evaluate a string command
+passed to a guard. Any attributes of the evaluating resource may be specified in
+the options that normally follow the guard's command string argument. For example:
+
+ # Tell Chef to use bash to interpret the guard string.
+ # Then we can use a guard command that is valid for bash
+ # but not for csh for instance
+ bash 'backupsettings' do
+ guard_interpreter :bash
+ code 'cp ~/appsettings.json ~/backup/appsettings.json'
+ not_if '[[ -e ./appsettings.json ]]', :cwd => '~/backup'
+ end
+
+The argument for `guard_interpreter` may be set to any of the following values:
+* The symbol name for a Chef Resource derived from the Chef `script` resource
+ such as `:bash`, `:powershell_script`, etc.
+* The symbol `:default` which means that a resource is not used to evaluate
+ the guard command argument, it is simply executed by the default shell as in
+ previous releases of Chef.
+
+By default, `guard_interpreter` is set to `:default` in this release.
+
+#### Attribute inheritance with `guard_interpreter`
+
+When `guard_interpreter` is not set to `:default`, the resource that evaluates the command will
+also inherit certain attribute values from the resource that contains the
+guard.
+
+Inherited attributes for all `script` resources are:
+
+* `:cwd`
+* `:environment`
+* `:group`
+* `:path`
+* `:user`
+* `:umask`
+
+For the `powershell_script` resource, the following attribute is inherited:
+* `:architecture`
+
+Inherited attributes may be overridden by specifying the same attribute as an
+argument to the guard itself.
+
+#### Guard inheritance example
+
+In the following example, the `:environment` hash only needs to be set once
+since the `bash` resource that execute the guard will inherit the same value:
+
+ script "javatooling" do
+ environment {"JAVA_HOME" => '/usr/lib/java/jdk1.7/home'}
+ code 'java-based-daemon-ctl.sh -start'
+ not_if 'java-based-daemon-ctl.sh -test-started' # No need to specify environment again
+ end
+
+### New `powershell_script` resource attribute: `convert_boolean_return`
+
+The `powershell_script` resource has a new attribute, `convert_boolean_return`
+that causes the script interpreter to return 0 if the last line of the command
+evaluted by PowerShell results in a boolean PowerShell data type that is true, or 1 if
+it results in a boolean PowerShell data type that is false. For example, the
+following two fragments will run successfully without error:
+
+ powershell_script 'false' do
+ code '$false'
+ end
+
+ powershell_script 'true' do
+ code '$true'
+ end
+
+But when `convert_boolean_return` is set to `true`, the "true" case above will
+still succeed, but the false case will raise an exception:
+
+ # Raises an exception
+ powershell_script 'false' do
+ convert_boolean_return true
+ code '$false'
+ end
+
+When used at recipe scope, the default value of `convert_boolean_return` is
+`false` in this release. However, if `guard_interpreter` is set to
+`:powershell_script`, the guard expression will be evaluted with a
+`powershell_script` resource that has the `convert_boolean_return` attribute
+set to `true`.
+
+#### Guard command example
+
+The behavior of `convert_boolean_return` is similar to the "$?"
+expression's value after use of the `test` command in Unix-flavored shells and
+its translation to an exit code for the shell. Since this attribute is set to
+`true` when `powershell_script` is used via the `guard_interpreter` to
+evaluate the guard expression, the behavior of `powershell_script` is very
+similar to guards executed with Unix shell interpreters as seen below:
+
+ bash 'make_safe_backup' do
+ code 'cp ~/data/nodes.json ~/data/nodes.bak'
+ not_if 'test -e ~/data/nodes.bak'
+ end
+
+ # convert_boolean_return is true by default in guards
+ powershell_script 'make_safe_backup' do
+ guard_interpreter :powershell_script
+ code 'cp ~/data/nodes.json ~/data/nodes.bak'
+ not_if 'test-path ~/data/nodes.bak'
+ end
diff --git a/Gemfile b/Gemfile
index 295f1781a0..1418235ebc 100644
--- a/Gemfile
+++ b/Gemfile
@@ -11,7 +11,7 @@ group(:development, :test) do
gem "simplecov"
gem 'rack', "~> 1.5.1"
- gem 'ruby-shadow', :platforms => :ruby unless RUBY_PLATFORM.downcase.match(/(darwin|freebsd|aix)/)
+ gem 'ruby-shadow', :platforms => :ruby unless RUBY_PLATFORM.downcase.match(/(aix|cygwin)/)
end
# If you want to load debugging tools into the bundle exec sandbox,
diff --git a/README.md b/README.md
index 61f2587673..1c8e58d115 100644
--- a/README.md
+++ b/README.md
@@ -61,7 +61,7 @@ Then get the source and install it:
Before working on the code, if you plan to contribute your changes, you need to
read the
-[Opscode Contributing document](http://docs.opscode.com/community_contributions.html).
+[Chef Contributions document](http://docs.opscode.com/community_contributions.html).
You will also need to set up the repository with the appropriate branches. We
document the process on the
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
new file mode 100644
index 0000000000..a4796c3e5a
--- /dev/null
+++ b/RELEASE_NOTES.md
@@ -0,0 +1,195 @@
+<!---
+This file is reset every time a new release is done. The contents of this file are for the currently unreleased version.
+
+Example Note:
+
+## Example Heading
+Details about the thing that changed that needs to get included in the Release Notes in markdown.
+-->
+# Chef Client Release Notes:
+
+#### `knife ssl check` and `knife ssl fetch` Commands
+
+As part of our process to transition to verifying SSL certificates by
+default, we've added knife commands to help you test (and fix, if
+needed) your SSL configuration.
+
+`knife ssl check` makes an SSL connection to your Chef server or any
+other HTTPS server and tells you if the server presents a valid
+certificate. If the certificate is not valid, knife will give further
+information about the cause and some instructions on how to remedy the
+issue. For example, if your Chef server uses an untrusted self-signed
+certificate:
+
+```
+ERROR: The SSL certificate of chefserver.test could not be
+verified
+Certificate issuer data:
+/C=US/ST=WA/L=Seattle/O=YouCorp/OU=Operations/CN=chefserver.test/emailAddress=you@example.com
+
+Configuration Info:
+
+OpenSSL Configuration:
+* Version: OpenSSL 1.0.1e 11 Feb 2013
+* Certificate file: /usr/local/etc/openssl/cert.pem
+* Certificate directory: /usr/local/etc/openssl/certs
+Chef SSL Configuration:
+* ssl_ca_path: nil
+* ssl_ca_file: nil
+* trusted_certs_dir: "/Users/ddeleo/.chef/trusted_certs"
+
+TO FIX THIS ERROR:
+
+If the server you are connecting to uses a self-signed certificate, you
+must
+configure chef to trust that server's certificate.
+
+By default, the certificate is stored in the following location on the
+host
+where your chef-server runs:
+
+ /var/opt/chef-server/nginx/ca/SERVER_HOSTNAME.crt
+
+Copy that file to you trusted_certs_dir (currently: /home/user/.chef/trusted_certs)
+using SSH/SCP or some other secure method, then re-run this command to confirm
+that the server's certificate is now trusted.
+```
+
+`knife ssl fetch` allows you to automatically fetch a server's
+certificates to your trusted certs directory. This provides an easy way
+to configure chef to trust your self-signed certificates. Note that
+knife cannot verify that the certificates haven't been tampered with, so
+you should verify their content after downloading.
+
+
+#### Unsecure SSL Verification Mode Now Triggers a Warning
+
+When `ssl_verify_mode` is set to `:verify_none`, Chef will print a
+warning. Use `knife ssl check` to test SSL connectivity and then add
+`ssl_verify_mode :verify_peer` to your configuration file to fix the
+warning. Though `:verify_none` is currently the default, this will be
+changed in a future release, so users are encouraged to be proactive in
+testing and updating their SSL configuration.
+
+#### Chef Solo Missing Dependency Warning ([CHEF-4367](https://tickets.opscode.com/browse/CHEF-4367))
+
+Chef 11.0 introduced ordered evaluation of non-recipe files in
+cookbooks, based on the dependencies specified in your cookbooks'
+metadata. This was a huge improvement on the previous behavior for all
+chef users, but it also introduced a problem for chef-solo users:
+because of the way chef-solo works, it was possible to use
+`include_recipe` to load a recipe from a cookbook without specifying the
+dependency in the metadata. This would load the recipe without having
+evaluated the associated attributes, libraries, LWRPs, etc. in that
+recipe's cookbook, and the recipe would fail to load with errors that
+did not suggest the actual cause of the failure.
+
+We've added a check to `include_recipe` so that attempting to include a
+recipe which is not a dependency of any cookbook specified in the run
+list will now log a warning with a message describing the problem and
+solution. In the future, this warning will become an error.
+
+#### Windows MSI Package Provider
+
+The first windows package provider has been added to core Chef. It supports Windows Installer (MSI) files only,
+and maintains idempotency by using the ProductCode from inside the MSI to determine if the products installation state.
+
+```
+package "install 7zip" do
+ action :install
+ source 'c:\downloads\7zip.msi'
+end
+```
+
+You can continue to use the windows_package LWRP from the windows cookbook alongside this provider.
+
+#### reboot_pending?
+
+We have added a ```reboot_pending?``` method to the recipe DSL. This method returns true or false if the operating system
+has a rebooting pending due to updates and a reboot being necessary to complete the installation. It does not report if a reboot has been requested, e.g. if someone has scheduled a restart using shutdown. It currently supports Windows and Ubuntu Linux.
+
+```
+Chef::Log.warn "There is a pending reboot, which will affect this Chef run" if reboot_pending?
+
+execute "Install Application" do
+ command 'C:\application\setup.exe'
+ not_if { reboot_pending? }
+end
+```
+
+#### FileEdit
+
+Chef::Util::FileEdit has been refactored into a Chef::Util::Editor class. The existing class continues to manage the files being edited while the new class handles the actual modification of the data.
+Along with this refactor, #insert_line_if_no_match can now manipulate a file multiple times. FileEdit also now has a #file_edited? method that can be used to tell if changes were made to the file on disk.
+
+#### DeepMerge sub-hash precedence bugfix ([CHEF-4918](https://tickets.opscode.com/browse/CHEF-4918))
+
+We discovered a bug where Chef incorrectly merged override attribute sub-hashes that were at least three levels deep as normal attributes.
+This has been corrected, and is not expected to cause any behavior change
+If you're an advanced user of attribute precedence, you may find some attributes were saved to your node object that you hadn't expected.
+
+#### Cron Resource
+
+The weekday attribute now accepts the weekday as a symbol, e.g. :monday or :thursday.
+There is a new attribute named ```time``` that takes special cron time values as a symbol, such as :reboot or :monthly.
+
+#### `guard_interpreter` attribute
+
+All Chef resources now support the `guard_interpreter` attribute, which
+enables you to use a Chef `script` such as `bash`, `powershell_script`,
+`perl`, etc., to evaluate the string command passed to a
+guard (i.e. `not_if` or `only_if` attribute). This addresses the related ticket
+[CHEF-4553](https://tickets.opscode.com/browse/CHEF-4453) which is concerned
+with the usability of the `powershell_script` resource, but also benefits
+users of resources like `python`, `bash`, etc:
+
+ # See CHEF-4553 -- let powershell_script execute the guard
+ powershell_script 'make_logshare' do
+ guard_interpreter :powershell_script
+ code 'new-smbshare logshare $env:systemdrive\\logs'
+ not_if 'get-smbshare logshare'
+ end
+
+#### `convert_boolean_return` attribute for `powershell_script`
+
+When set to `true`, the `convert_boolean_return` attribute will allow any script executed by
+`powershell_script` that exits with a PowerShell boolean data type to convert
+PowerShell boolean `$true` to exit status 0 and `$false` to exit status 1.
+
+The new attribute defaults to `false` except when the `powershell_script` resource is executing script passed to a guard attribute
+via the `guard_interpreter` attribute in which case it is `true` by default.
+
+#### knife bootstrap log_level
+
+Running ```knife bootstrap -V -V``` will run the initial chef-client with a log level of debug.
+
+#### knife cookbook test
+
+Knife cookbook test now respects [chefignore files](http://docs.opscode.com/essentials_repository.html#chefignore-files), allowing you to exclude unrelated ruby code such as unit tests.
+
+#### Miscellaneous
+
+* The subversion resource will now mask plaintext passwords in error output.
+* The debian pkg provider now supports epochs in the version string.
+* When a cookbook upload is missing multiple dependencies, all of them are now listed.
+* knife node run_list add now supports a --before option.
+
+#### OHAI 7
+
+After spending 3 months in the RC stage, OHAI 7 is now included in Chef Client 11.10.0. Note that Chef Client 10.32.0 still includes OHAI 6.
+
+For more information about the changes in OHAI 7 please see our previous blog post [here](http://www.getchef.com/blog/2014/01/20/ohai-7-0-release-candidate/).
+
+# Chef Client Breaking Changes:
+
+#### OpenSuse and Suse Differentiation
+
+The Ohai version currently included in Chef reports both SUSE and OpenSUSE platforms as "suse" and the way to differentiate between these two platforms has been to use the version numbers. But since SUSE version numbers have caught up with OpenSUSE, it's not possible to differentiate between these platforms anymore.
+
+This issue is being resolved in Ohai 7 that is included in the current release of Chef Client by reporting these two platforms separately. This resolves the overall problem however it's a breaking change in the sense that OpenSUSE platforms will be reported as "opensuse" as the platform.
+
+Normally Chef would require a major version bump for this change but since the original scenario is currently broken we've decided to include this change without a major version bump in Chef.
+
+If you need to differentiate between OpenSUSE and SUSE in your cookbooks, please make sure the differentiation logic is updated to use the new :platform attribute values rather than the :platform_version in your cookbooks before upgrading to this version.
+
+None.
diff --git a/chef.gemspec b/chef.gemspec
index fcc585c363..12084e30e4 100644
--- a/chef.gemspec
+++ b/chef.gemspec
@@ -16,8 +16,8 @@ Gem::Specification.new do |s|
s.add_dependency "mixlib-cli", "~> 1.4"
s.add_dependency "mixlib-log", "~> 1.3"
s.add_dependency "mixlib-authentication", "~> 1.3"
- s.add_dependency "mixlib-shellout", "~> 1.3"
- s.add_dependency "ohai", "~> 6.0"
+ s.add_dependency "mixlib-shellout", "= 1.4.0.rc.0"
+ s.add_dependency "ohai", "= 7.0.0.rc.1"
s.add_dependency "rest-client", ">= 1.0.4", "< 1.7.0"
# rest-client has an unbounded dependency on mime-types.
@@ -36,12 +36,15 @@ Gem::Specification.new do |s|
s.add_dependency "erubis", "~> 2.7"
s.add_dependency "diff-lcs", "~> 1.2", ">= 1.2.4"
- s.add_dependency "chef-zero", "~> 1.7", ">= 1.7.2"
- s.add_dependency "puma", "~> 1.6"
+ # There's a bug with Chef Zero and IPV6 prior to version 2.0.2
+ s.add_dependency "chef-zero", "~> 2.0", ">= 2.0.2"
s.add_dependency "pry", "~> 0.9"
- %w(rake rack rspec_junit_formatter).each { |gem| s.add_development_dependency gem }
+ # Rake 10.2 drops Ruby 1.8 support
+ s.add_development_dependency("rake", "~> 10.1.0")
+
+ %w( rack rspec_junit_formatter).each { |gem| s.add_development_dependency gem }
%w(rspec-core rspec-expectations rspec-mocks).each { |gem| s.add_development_dependency gem, "~> 2.14.0" }
s.bindir = "bin"
diff --git a/lib/chef/api_client.rb b/lib/chef/api_client.rb
index 66cbd3f30e..7b7fd99ff7 100644
--- a/lib/chef/api_client.rb
+++ b/lib/chef/api_client.rb
@@ -162,9 +162,7 @@ class Chef
if response.kind_of?(Chef::ApiClient)
response
else
- client = Chef::ApiClient.new
- client.name(response['clientname'])
- client
+ json_create(response)
end
end
diff --git a/lib/chef/api_client/registration.rb b/lib/chef/api_client/registration.rb
index f44c326d5d..213d0b7f49 100644
--- a/lib/chef/api_client/registration.rb
+++ b/lib/chef/api_client/registration.rb
@@ -30,14 +30,13 @@ class Chef
# a new client/node identity by borrowing the validator client identity
# when creating a new client.
class Registration
- attr_reader :private_key
attr_reader :destination
attr_reader :name
def initialize(name, destination)
@name = name
@destination = destination
- @private_key = nil
+ @server_generated_private_key = nil
end
# Runs the client registration process, including creating the client on
@@ -90,29 +89,67 @@ class Chef
end
def create
- response = http_api.post("clients", :name => name, :admin => false)
- @private_key = response["private_key"]
+ response = http_api.post("clients", post_data)
+ @server_generated_private_key = response["private_key"]
response
end
def update
- response = http_api.put("clients/#{name}", :name => name,
- :admin => false,
- :private_key => true)
+ response = http_api.put("clients/#{name}", put_data)
if response.respond_to?(:private_key) # Chef 11
- @private_key = response.private_key
+ @server_generated_private_key = response.private_key
else # Chef 10
- @private_key = response["private_key"]
+ @server_generated_private_key = response["private_key"]
end
response
end
+ def put_data
+ base_put_data = { :name => name, :admin => false }
+ if self_generate_keys?
+ base_put_data[:public_key] = generated_public_key
+ else
+ base_put_data[:private_key] = true
+ end
+ base_put_data
+ end
+
+ def post_data
+ post_data = { :name => name, :admin => false }
+ post_data[:public_key] = generated_public_key if self_generate_keys?
+ post_data
+ end
+
+
def http_api
@http_api_as_validator ||= Chef::REST.new(Chef::Config[:chef_server_url],
Chef::Config[:validation_client_name],
Chef::Config[:validation_key])
end
+ # Whether or not to generate keys locally and post the public key to the
+ # server. Delegates to `Chef::Config.local_key_generation`. Servers
+ # before 11.0 do not support this feature.
+ def self_generate_keys?
+ Chef::Config.local_key_generation
+ end
+
+ def private_key
+ if self_generate_keys?
+ generated_private_key.to_pem
+ else
+ @server_generated_private_key
+ end
+ end
+
+ def generated_private_key
+ @generated_key ||= OpenSSL::PKey::RSA.generate(2048)
+ end
+
+ def generated_public_key
+ generated_private_key.public_key.to_pem
+ end
+
def file_flags
base_flags = File::CREAT|File::TRUNC|File::RDWR
# Windows doesn't have symlinks, so it doesn't have NOFOLLOW
diff --git a/lib/chef/application.rb b/lib/chef/application.rb
index 04e88de2ce..601bbd91f1 100644
--- a/lib/chef/application.rb
+++ b/lib/chef/application.rb
@@ -208,7 +208,8 @@ class Chef::Application
@chef_client = Chef::Client.new(
@chef_client_json,
:override_runlist => config[:override_runlist],
- :specific_recipes => specific_recipes
+ :specific_recipes => specific_recipes,
+ :runlist => config[:runlist]
)
@chef_client_json = nil
diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb
index de644b5f31..c579fe4ba1 100644
--- a/lib/chef/application/client.rb
+++ b/lib/chef/application/client.rb
@@ -25,7 +25,6 @@ require 'chef/log'
require 'chef/config_fetcher'
require 'chef/handler/error_report'
-
class Chef::Application::Client < Chef::Application
# Mimic self_pipe sleep from Unicorn to capture signals safely
@@ -170,7 +169,7 @@ class Chef::Application::Client < Chef::Application
option :override_runlist,
:short => "-o RunlistItem,RunlistItem...",
:long => "--override-runlist RunlistItem,RunlistItem...",
- :description => "Replace current run list with specified items",
+ :description => "Replace current run list with specified items for a single run",
:proc => lambda{|items|
items = items.split(',')
items.compact.map{|item|
@@ -178,6 +177,16 @@ class Chef::Application::Client < Chef::Application
}
}
+ option :runlist,
+ :short => "-r RunlistItem,RunlistItem...",
+ :long => "--runlist RunlistItem,RunlistItem...",
+ :description => "Permanently replace current run list with specified items",
+ :proc => lambda{|items|
+ items = items.split(',')
+ items.compact.map{|item|
+ Chef::RunList::RunListItem.new(item)
+ }
+ }
option :why_run,
:short => '-W',
:long => '--why-run',
@@ -218,12 +227,10 @@ class Chef::Application::Client < Chef::Application
:boolean => true
end
- attr_reader :chef_client_json
+ IMMEDIATE_RUN_SIGNAL = "1".freeze
+ GRACEFUL_EXIT_SIGNAL = "2".freeze
- def initialize
- super
- @exit_gracefully = false
- end
+ attr_reader :chef_client_json
# Reconfigure the chef client
# Re-open the JSON attributes and load them into the node
@@ -285,13 +292,12 @@ class Chef::Application::Client < Chef::Application
trap("USR1") do
Chef::Log.info("SIGUSR1 received, waking up")
- SELF_PIPE[1].putc('.') # wakeup master process from select
+ SELF_PIPE[1].putc(IMMEDIATE_RUN_SIGNAL) # wakeup master process from select
end
trap("TERM") do
Chef::Log.info("SIGTERM received, exiting gracefully")
- @exit_gracefully = true
- SELF_PIPE[1].putc('.')
+ SELF_PIPE[1].putc(GRACEFUL_EXIT_SIGNAL)
end
end
@@ -303,23 +309,24 @@ class Chef::Application::Client < Chef::Application
Chef::Daemon.daemonize("chef-client")
end
+ signal = nil
+
loop do
begin
- Chef::Application.exit!("Exiting", 0) if @exit_gracefully
- if Chef::Config[:splay]
+ Chef::Application.exit!("Exiting", 0) if signal == GRACEFUL_EXIT_SIGNAL
+
+ if Chef::Config[:splay] and signal != IMMEDIATE_RUN_SIGNAL
splay = rand Chef::Config[:splay]
Chef::Log.debug("Splay sleep #{splay} seconds")
sleep splay
end
+
+ signal = nil
run_chef_client(Chef::Config[:specific_recipes])
+
if Chef::Config[:interval]
Chef::Log.debug("Sleeping for #{Chef::Config[:interval]} seconds")
- unless SELF_PIPE.empty?
- client_sleep Chef::Config[:interval]
- else
- # Windows
- sleep Chef::Config[:interval]
- end
+ signal = interval_sleep
else
Chef::Application.exit! "Exiting", 0
end
@@ -329,12 +336,7 @@ class Chef::Application::Client < Chef::Application
if Chef::Config[:interval]
Chef::Log.error("#{e.class}: #{e}")
Chef::Log.error("Sleeping for #{Chef::Config[:interval]} seconds before trying again")
- unless SELF_PIPE.empty?
- client_sleep Chef::Config[:interval]
- else
- # Windows
- sleep Chef::Config[:interval]
- end
+ signal = interval_sleep
retry
else
Chef::Application.fatal!("#{e.class}: #{e.message}", 1)
@@ -345,8 +347,17 @@ class Chef::Application::Client < Chef::Application
private
+ def interval_sleep
+ unless SELF_PIPE.empty?
+ client_sleep Chef::Config[:interval]
+ else
+ # Windows
+ sleep Chef::Config[:interval]
+ end
+ end
+
def client_sleep(sec)
IO.select([ SELF_PIPE[0] ], nil, nil, sec) or return
- SELF_PIPE[0].getc
+ SELF_PIPE[0].getc.chr
end
end
diff --git a/lib/chef/client.rb b/lib/chef/client.rb
index 722c9915e9..2e5963e996 100644
--- a/lib/chef/client.rb
+++ b/lib/chef/client.rb
@@ -44,6 +44,7 @@ require 'chef/version'
require 'chef/resource_reporter'
require 'chef/run_lock'
require 'chef/policy_builder'
+require 'chef/request_id'
require 'ohai'
require 'rbconfig'
@@ -54,6 +55,16 @@ class Chef
class Client
include Chef::Mixin::PathSanity
+ # IO stream that will be used as 'STDOUT' for formatters. Formatters are
+ # configured during `initialize`, so this provides a convenience for
+ # setting alternative IO stream during tests.
+ STDOUT_FD = STDOUT
+
+ # IO stream that will be used as 'STDERR' for formatters. Formatters are
+ # configured during `initialize`, so this provides a convenience for
+ # setting alternative IO stream during tests.
+ STDERR_FD = STDERR
+
# Clears all notifications for client run status events.
# Primarily for testing purposes.
def self.clear_notifications
@@ -128,15 +139,13 @@ class Chef
attr_accessor :rest
attr_accessor :runner
- #--
- # TODO: timh/cw: 5-19-2010: json_attribs should be moved to RunContext?
attr_reader :json_attribs
attr_reader :run_status
attr_reader :events
# Creates a new Chef::Client.
def initialize(json_attribs=nil, args={})
- @json_attribs = json_attribs
+ @json_attribs = json_attribs || {}
@node = nil
@run_status = nil
@runner = nil
@@ -148,12 +157,16 @@ class Chef
@events = EventDispatch::Dispatcher.new(*event_handlers)
@override_runlist = args.delete(:override_runlist)
@specific_recipes = args.delete(:specific_recipes)
+
+ if new_runlist = args.delete(:runlist)
+ @json_attribs["run_list"] = new_runlist
+ end
end
def configure_formatters
formatters_for_run.map do |formatter_name, output_path|
if output_path.nil?
- Chef::Formatters.new(formatter_name, STDOUT, STDERR)
+ Chef::Formatters.new(formatter_name, STDOUT_FD, STDERR_FD)
else
io = File.open(output_path, "a+")
io.sync = true
@@ -280,13 +293,10 @@ class Chef
end
def node_name
- name = Chef::Config[:node_name] || ohai[:fqdn] || ohai[:hostname]
+ name = Chef::Config[:node_name] || ohai[:fqdn] || ohai[:machinename] || ohai[:hostname]
Chef::Config[:node_name] = name
- unless name
- msg = "Unable to determine node name: configure node_name or configure the system's hostname and fqdn"
- raise Chef::Exceptions::CannotDetermineNodeName, msg
- end
+ raise Chef::Exceptions::CannotDetermineNodeName unless name
# node names > 90 bytes only work with authentication protocol >= 1.1
# see discussion in config.rb.
@@ -391,10 +401,15 @@ class Chef
# don't add code that may fail before entering this section to be sure to release lock
begin
runlock.save_pid
+
+ check_ssl_config
+
+ request_id = Chef::RequestID.instance.request_id
run_context = nil
@events.run_start(Chef::VERSION)
Chef::Log.info("*** Chef #{Chef::VERSION} ***")
Chef::Log.info "Chef-client pid: #{Process.pid}"
+ Chef::Log.debug("Chef-client request_id: #{request_id}")
enforce_path_sanity
run_ohai
@events.ohai_completed(node)
@@ -404,6 +419,7 @@ class Chef
build_node
+ run_status.run_id = request_id
run_status.start_clock
Chef::Log.info("Starting Chef Run for #{node.name}")
run_started
@@ -434,6 +450,8 @@ class Chef
@events.run_failed(e)
raise
ensure
+ Chef::RequestID.instance.reset_request_id
+ request_id = nil
@run_status = nil
run_context = nil
runlock.release
@@ -474,6 +492,37 @@ class Chef
Chef::ReservedNames::Win32::Security.has_admin_privileges?
end
+ def check_ssl_config
+ if Chef::Config[:ssl_verify_mode] == :verify_none and !Chef::Config[:verify_api_cert]
+ Chef::Log.warn(<<-WARN)
+
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+SSL validation of HTTPS requests is disabled. HTTPS connections are still
+encrypted, but chef is not able to detect forged replies or man in the middle
+attacks.
+
+To fix this issue add an entry like this to your configuration file:
+
+```
+ # Verify all HTTPS connections (recommended)
+ ssl_verify_mode :verify_peer
+
+ # OR, Verify only connections to chef-server
+ verify_api_cert true
+```
+
+To check your SSL configuration, or troubleshoot errors, you can use the
+`knife ssl check` command like so:
+
+```
+ knife ssl check -c #{Chef::Config.config_file}
+```
+
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+WARN
+ end
+ end
+
end
end
diff --git a/lib/chef/config.rb b/lib/chef/config.rb
index da3f3790f6..3099d876c1 100644
--- a/lib/chef/config.rb
+++ b/lib/chef/config.rb
@@ -432,6 +432,17 @@ class Chef
default(:validation_key) { chef_zero.enabled ? nil : platform_specific_path("/etc/chef/validation.pem") }
default :validation_client_name, "chef-validator"
+ # When creating a new client via the validation_client account, Chef 11
+ # servers allow the client to generate a key pair locally and sent the
+ # public key to the server. This is more secure and helps offload work from
+ # the server, enhancing scalability. If enabled and the remote server
+ # implements only the Chef 10 API, client registration will not work
+ # properly.
+ #
+ # The default value is `false` (Server generates client keys). Set to
+ # `true` to enable client-side key generation.
+ default(:local_key_generation) { false }
+
# Zypper package provider gpg checks. Set to true to enable package
# gpg signature checking. This will be default in the
# future. Setting to false disables the warnings.
diff --git a/lib/chef/cookbook/chefignore.rb b/lib/chef/cookbook/chefignore.rb
index 17c000350d..aa9345e64e 100644
--- a/lib/chef/cookbook/chefignore.rb
+++ b/lib/chef/cookbook/chefignore.rb
@@ -25,7 +25,11 @@ class Chef
attr_reader :ignores
def initialize(ignore_file_or_repo)
+ # Check the 'ignore_file_or_repo' path first and then look in the parent directory
+ # to handle both the chef repo cookbook layout and a standalone cookbook
@ignore_file = find_ignore_file(ignore_file_or_repo)
+ @ignore_file = find_ignore_file(File.dirname(ignore_file_or_repo)) unless readable_file_or_symlink?(@ignore_file)
+
@ignores = parse_ignore_file
end
@@ -43,8 +47,7 @@ class Chef
def parse_ignore_file
ignore_globs = []
- if File.exist?(@ignore_file) && File.readable?(@ignore_file) &&
- (File.file?(@ignore_file) || File.symlink?(@ignore_file))
+ if readable_file_or_symlink?(@ignore_file)
File.foreach(@ignore_file) do |line|
ignore_globs << line.strip unless line =~ COMMENTS_AND_WHITESPACE
end
@@ -61,6 +64,11 @@ class Chef
File.join(path, 'chefignore')
end
end
+
+ def readable_file_or_symlink?(path)
+ File.exist?(@ignore_file) && File.readable?(@ignore_file) &&
+ (File.file?(@ignore_file) || File.symlink?(@ignore_file))
+ end
end
end
end
diff --git a/lib/chef/cookbook/metadata.rb b/lib/chef/cookbook/metadata.rb
index b9b32c8224..32597490d3 100644
--- a/lib/chef/cookbook/metadata.rb
+++ b/lib/chef/cookbook/metadata.rb
@@ -391,14 +391,14 @@ class Chef
:description => { :kind_of => String },
:choice => { :kind_of => [ Array ], :default => [] },
:calculated => { :equal_to => [ true, false ], :default => false },
- :type => { :equal_to => [ "string", "array", "hash", "symbol" ], :default => "string" },
+ :type => { :equal_to => [ "string", "array", "hash", "symbol", "boolean", "numeric" ], :default => "string" },
:required => { :equal_to => [ "required", "recommended", "optional", true, false ], :default => "optional" },
:recipes => { :kind_of => [ Array ], :default => [] },
- :default => { :kind_of => [ String, Array, Hash ] }
+ :default => { :kind_of => [ String, Array, Hash, Symbol, Numeric, TrueClass, FalseClass ] }
}
)
options[:required] = remap_required_attribute(options[:required]) unless options[:required].nil?
- validate_string_array(options[:choice])
+ validate_choice_array(options)
validate_calculated_default_rule(options)
validate_choice_default_rule(options)
@@ -546,6 +546,34 @@ INVALID
end
end
+ # Validate the choice of the options hash
+ #
+ # Raise an exception if the members of the array do not match the defaults
+ # === Parameters
+ # opts<Hash>:: The options hash
+ def validate_choice_array(opts)
+ if opts[:choice].kind_of?(Array)
+ case opts[:type]
+ when "string"
+ validator = [ String ]
+ when "array"
+ validator = [ Array ]
+ when "hash"
+ validator = [ Hash ]
+ when "symbol"
+ validator = [ Symbol ]
+ when "boolean"
+ validator = [ TrueClass, FalseClass ]
+ when "numeric"
+ validator = [ Numeric ]
+ end
+
+ opts[:choice].each do |choice|
+ validate( {:choice => choice}, {:choice => {:kind_of => validator}} )
+ end
+ end
+ end
+
# For backwards compatibility, remap Boolean values to String
# true is mapped to "required"
# false is mapped to "optional"
diff --git a/lib/chef/cookbook/synchronizer.rb b/lib/chef/cookbook/synchronizer.rb
index 4522323fac..fc5d16617c 100644
--- a/lib/chef/cookbook/synchronizer.rb
+++ b/lib/chef/cookbook/synchronizer.rb
@@ -92,7 +92,7 @@ class Chef
# === Returns
# true:: Always returns true
def sync_cookbooks
- Chef::Log.info("Loading cookbooks [#{cookbook_names.sort.join(', ')}]")
+ Chef::Log.info("Loading cookbooks [#{cookbooks.map {|ckbk| ckbk.name + '@' + ckbk.version}.join(', ')}]")
Chef::Log.debug("Cookbooks detail: #{cookbooks.inspect}")
clear_obsoleted_cookbooks
@@ -136,7 +136,7 @@ class Chef
# valid_cache_entries<Hash>:: Out-param; Added to this hash are the files that
# were referred to by this cookbook
def sync_cookbook(cookbook)
- Chef::Log.debug("Synchronizing cookbook #{cookbook.name}")
+ Chef::Log.debug("Synchronizing cookbook #{cookbook.name} #{cookbook.version}")
# files and templates are lazily loaded, and will be done later.
diff --git a/lib/chef/cookbook/syntax_check.rb b/lib/chef/cookbook/syntax_check.rb
index 59888e2ba3..effc7dd01d 100644
--- a/lib/chef/cookbook/syntax_check.rb
+++ b/lib/chef/cookbook/syntax_check.rb
@@ -17,6 +17,8 @@
#
require 'pathname'
+require 'stringio'
+require 'erubis'
require 'chef/mixin/shell_out'
require 'chef/mixin/checksum'
@@ -75,6 +77,8 @@ class Chef
# validated.
attr_reader :validated_files
+ attr_reader :chefignore
+
# Creates a new SyntaxCheck given the +cookbook_name+ and a +cookbook_path+.
# If no +cookbook_path+ is given, +Chef::Config.cookbook_path+ is used.
def self.for_cookbook(cookbook_name, cookbook_path=nil)
@@ -90,11 +94,9 @@ class Chef
# cookbook_path::: the (on disk) path to the cookbook
def initialize(cookbook_path)
@cookbook_path = cookbook_path
- @validated_files = PersistentSet.new
- end
+ @chefignore ||= Chefignore.new(cookbook_path)
- def chefignore
- @chefignore ||= Chefignore.new(File.dirname(cookbook_path))
+ @validated_files = PersistentSet.new
end
def remove_ignored_files(file_list)
@@ -161,28 +163,127 @@ class Chef
def validate_template(erb_file)
Chef::Log.debug("Testing template #{erb_file} for syntax errors...")
- result = shell_out("erubis -x #{erb_file} | ruby -c")
+ if validate_inline?
+ validate_erb_file_inline(erb_file)
+ else
+ validate_erb_via_subcommand(erb_file)
+ end
+ end
+
+ def validate_ruby_file(ruby_file)
+ Chef::Log.debug("Testing #{ruby_file} for syntax errors...")
+ if validate_inline?
+ validate_ruby_file_inline(ruby_file)
+ else
+ validate_ruby_by_subcommand(ruby_file)
+ end
+ end
+
+ # Whether or not we're running on a version of ruby that can support
+ # inline validation. Inline validation relies on the `RubyVM` features
+ # introduced with ruby 1.9, so 1.8 cannot be supported.
+ def validate_inline?
+ defined?(RubyVM::InstructionSequence)
+ end
+
+ # Validate the ruby code in an erb template. Uses RubyVM to do syntax
+ # checking, so callers should check #validate_inline? before calling.
+ def validate_erb_file_inline(erb_file)
+ old_stderr = $stderr
+
+ engine = Erubis::Eruby.new
+ engine.convert!(IO.read(erb_file))
+
+ ruby_code = engine.src
+
+ # Even when we're compiling the code w/ RubyVM, syntax errors just
+ # print to $stderr. We want to capture this and handle the printing
+ # ourselves, so we must temporarily swap $stderr to capture the output.
+ tmp_stderr = $stderr = StringIO.new
+
+ abs_path = File.expand_path(erb_file)
+ RubyVM::InstructionSequence.new(ruby_code, erb_file, abs_path, 0)
+
+ true
+ rescue SyntaxError
+ $stderr = old_stderr
+ invalid_erb_file(erb_file, tmp_stderr.string)
+ false
+ ensure
+ # be paranoid about setting stderr back to the old value.
+ $stderr = old_stderr if defined?(old_stderr) && old_stderr
+ end
+
+ # Validate the ruby code in an erb template. Pipes the output of `erubis
+ # -x` to `ruby -c`, so it works with any ruby version, but is much slower
+ # than the inline version.
+ # --
+ # TODO: This can be removed when ruby 1.8 support is dropped.
+ def validate_erb_via_subcommand(erb_file)
+ result = shell_out("erubis -x #{erb_file} | #{ruby} -c")
result.error!
true
rescue Mixlib::ShellOut::ShellCommandFailed
+ invalid_erb_file(erb_file, result.stderr)
+ false
+ end
+
+ # Debug a syntax error in a template.
+ def invalid_erb_file(erb_file, error_message)
file_relative_path = erb_file[/^#{Regexp.escape(cookbook_path+File::Separator)}(.*)/, 1]
Chef::Log.fatal("Erb template #{file_relative_path} has a syntax error:")
- result.stderr.each_line { |l| Chef::Log.fatal(l.chomp) }
+ error_message.each_line { |l| Chef::Log.fatal(l.chomp) }
+ nil
+ end
+
+ # Validate the syntax of a ruby file. Uses (Ruby 1.9+ only) RubyVM to
+ # compile the code without evaluating it or spawning a new process.
+ # Callers should check #validate_inline? before calling.
+ def validate_ruby_file_inline(ruby_file)
+ # Even when we're compiling the code w/ RubyVM, syntax errors just
+ # print to $stderr. We want to capture this and handle the printing
+ # ourselves, so we must temporarily swap $stderr to capture the output.
+ old_stderr = $stderr
+ tmp_stderr = $stderr = StringIO.new
+ abs_path = File.expand_path(ruby_file)
+ file_content = IO.read(abs_path)
+ RubyVM::InstructionSequence.new(file_content, ruby_file, abs_path, 0)
+ true
+ rescue SyntaxError
+ $stderr = old_stderr
+ invalid_ruby_file(ruby_file, tmp_stderr.string)
false
+ ensure
+ # be paranoid about setting stderr back to the old value.
+ $stderr = old_stderr if defined?(old_stderr) && old_stderr
end
- def validate_ruby_file(ruby_file)
- Chef::Log.debug("Testing #{ruby_file} for syntax errors...")
- result = shell_out("ruby -c #{ruby_file}")
+ # Validate the syntax of a ruby file by shelling out to `ruby -c`. Should
+ # work for all ruby versions, but is slower and uses more resources than
+ # the inline strategy.
+ def validate_ruby_by_subcommand(ruby_file)
+ result = shell_out("#{ruby} -c #{ruby_file}")
result.error!
true
rescue Mixlib::ShellOut::ShellCommandFailed
+ invalid_ruby_file(ruby_file, result.stderr)
+ false
+ end
+
+ # Debugs ruby syntax errors by printing the path to the file and any
+ # diagnostic info given in +error_message+
+ def invalid_ruby_file(ruby_file, error_message)
file_relative_path = ruby_file[/^#{Regexp.escape(cookbook_path+File::Separator)}(.*)/, 1]
Chef::Log.fatal("Cookbook file #{file_relative_path} has a ruby syntax error:")
- result.stderr.each_line { |l| Chef::Log.fatal(l.chomp) }
+ error_message.each_line { |l| Chef::Log.fatal(l.chomp) }
false
end
+ # Returns the full path to the running ruby.
+ def ruby
+ Gem.ruby
+ end
+
end
end
end
diff --git a/lib/chef/dsl/reboot_pending.rb b/lib/chef/dsl/reboot_pending.rb
new file mode 100644
index 0000000000..9f80d38c61
--- /dev/null
+++ b/lib/chef/dsl/reboot_pending.rb
@@ -0,0 +1,61 @@
+# Author:: Bryan McLellan <btm@loftninjas.org>
+# Author:: Seth Chisamore <schisamo@opscode.com>
+# Copyright:: Copyright (c) 2011,2014, 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/dsl/platform_introspection'
+require 'chef/dsl/registry_helper'
+
+class Chef
+ module DSL
+ module RebootPending
+
+ include Chef::DSL::RegistryHelper
+ include Chef::DSL::PlatformIntrospection
+
+ # Returns true if the system needs a reboot or is expected to reboot
+ # Raises UnsupportedPlatform if this functionality isn't provided yet
+ def reboot_pending?
+
+ if platform?("windows")
+ # PendingFileRenameOperations contains pairs (REG_MULTI_SZ) of filenames that cannot be updated
+ # due to a file being in use (usually a temporary file and a system file)
+ # \??\c:\temp\test.sys!\??\c:\winnt\system32\test.sys
+ # http://technet.microsoft.com/en-us/library/cc960241.aspx
+ registry_value_exists?('HKLM\SYSTEM\CurrentControlSet\Control\Session Manager', { :name => 'PendingFileRenameOperations' }) ||
+
+ # RebootRequired key contains Update IDs with a value of 1 if they require a reboot.
+ # The existence of RebootRequired alone is sufficient on my Windows 8.1 workstation in Windows Update
+ registry_key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired') ||
+
+ # Vista + Server 2008 and newer may have reboots pending from CBS
+ registry_key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired') ||
+
+ # The mere existance of the UpdateExeVolatile key should indicate a pending restart for certain updates
+ # http://support.microsoft.com/kb/832475
+ (registry_key_exists?('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile') &&
+ !registry_get_values('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').select { |v| v[:name] == "Flags" }[0].nil? &&
+ [1,2,3].include?(registry_get_values('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').select { |v| v[:name] == "Flags" }[0][:data]))
+ elsif platform?("ubuntu")
+ # This should work for Debian as well if update-notifier-common happens to be installed. We need an API for that.
+ File.exists?('/var/run/reboot-required')
+ else
+ raise Chef::Exceptions::UnsupportedPlatform.new(node[:platform])
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/encrypted_data_bag_item.rb b/lib/chef/encrypted_data_bag_item.rb
index b38a6f3512..b0d9337212 100644
--- a/lib/chef/encrypted_data_bag_item.rb
+++ b/lib/chef/encrypted_data_bag_item.rb
@@ -26,7 +26,7 @@ require 'open-uri'
# all values, except for the value associated with the id key, have
# been encrypted.
#
-# EncrypedDataBagItem can be used in recipes to decrypt data bag item
+# EncryptedDataBagItem can be used in recipes to decrypt data bag item
# members.
#
# Data bag item values are assumed to have been encrypted using the
@@ -49,6 +49,22 @@ require 'open-uri'
class Chef::EncryptedDataBagItem
ALGORITHM = 'aes-256-cbc'
+ #
+ # === Synopsis
+ #
+ # EncryptedDataBagItem.new(hash, secret)
+ #
+ # === Args
+ #
+ # +enc_hash+::
+ # The encrypted hash to be decrypted
+ # +secret+::
+ # The raw secret key
+ #
+ # === Description
+ #
+ # Create a new encrypted data bag item for reading (decryption)
+ #
def initialize(enc_hash, secret)
@enc_hash = enc_hash
@secret = secret
@@ -82,6 +98,26 @@ class Chef::EncryptedDataBagItem
end
end
+ #
+ # === Synopsis
+ #
+ # EncryptedDataBagItem.load(data_bag, name, secret = nil)
+ #
+ # === Args
+ #
+ # +data_bag+::
+ # The name of the data bag to fetch
+ # +name+::
+ # The name of the data bag item to fetch
+ # +secret+::
+ # The raw secret key. If the +secret+ is nil, the value of the file at
+ # +Chef::Config[:encrypted_data_bag_secret]+ is loaded. See +load_secret+
+ # for more information.
+ #
+ # === Description
+ #
+ # Loads and decrypts the data bag item with the given name.
+ #
def self.load(data_bag, name, secret = nil)
raw_hash = Chef::DataBagItem.load(data_bag, name)
secret = secret || self.load_secret
diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb
index afd42885f9..bd99cb3ebd 100644
--- a/lib/chef/exceptions.rb
+++ b/lib/chef/exceptions.rb
@@ -50,7 +50,13 @@ class Chef
class Override < RuntimeError; end
class UnsupportedAction < RuntimeError; end
class MissingLibrary < RuntimeError; end
- class CannotDetermineNodeName < RuntimeError; end
+
+ class CannotDetermineNodeName < RuntimeError
+ def initialize
+ super "Unable to determine node name: configure node_name or configure the system's hostname and fqdn"
+ end
+ end
+
class User < RuntimeError; end
class Group < RuntimeError; end
class Link < RuntimeError; end
@@ -70,6 +76,7 @@ class Chef
class CookbookNotFoundInRepo < ArgumentError; end
class RecipeNotFound < ArgumentError; end
class AttributeNotFound < RuntimeError; end
+ class MissingCookbookDependency < StandardError; end # CHEF-5120
class InvalidCommandOption < RuntimeError; end
class CommandTimeout < RuntimeError; end
class RequestedUIDUnavailable < RuntimeError; end
@@ -309,5 +316,10 @@ class Chef
end
end
+ class UnsupportedPlatform < RuntimeError
+ def initialize(platform)
+ super "This functionality is not supported on platform #{platform}."
+ end
+ end
end
end
diff --git a/lib/chef/formatters/error_descriptor.rb b/lib/chef/formatters/error_descriptor.rb
index 3f0756df73..c2e656f167 100644
--- a/lib/chef/formatters/error_descriptor.rb
+++ b/lib/chef/formatters/error_descriptor.rb
@@ -31,7 +31,7 @@ class Chef
end
def section(heading, text)
- @sections << {heading => text}
+ @sections << {heading => (text or "")}
end
def display(out)
diff --git a/lib/chef/guard_interpreter/default_guard_interpreter.rb b/lib/chef/guard_interpreter/default_guard_interpreter.rb
new file mode 100644
index 0000000000..df91c2b1ad
--- /dev/null
+++ b/lib/chef/guard_interpreter/default_guard_interpreter.rb
@@ -0,0 +1,42 @@
+#
+# Author:: Adam Edwards (<adamed@getchef.com>)
+# Copyright:: Copyright (c) 2014 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.
+#
+
+class Chef
+ class GuardInterpreter
+ class DefaultGuardInterpreter
+ include Chef::Mixin::ShellOut
+
+ protected
+
+ def initialize(command, opts)
+ @command = command
+ @command_opts = opts
+ end
+
+ public
+
+ def evaluate
+ shell_out(@command, @command_opts).status.success?
+ rescue Chef::Exceptions::CommandTimeout
+ Chef::Log.warn "Command '#{@command}' timed out"
+ false
+ end
+ end
+ end
+end
+
diff --git a/lib/chef/guard_interpreter/resource_guard_interpreter.rb b/lib/chef/guard_interpreter/resource_guard_interpreter.rb
new file mode 100644
index 0000000000..229a8502c7
--- /dev/null
+++ b/lib/chef/guard_interpreter/resource_guard_interpreter.rb
@@ -0,0 +1,122 @@
+#
+# Author:: Adam Edwards (<adamed@getchef.com>)
+# Copyright:: Copyright (c) 2014 Opscode, 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/guard_interpreter/default_guard_interpreter'
+
+class Chef
+ class GuardInterpreter
+ class ResourceGuardInterpreter < DefaultGuardInterpreter
+
+ def initialize(parent_resource, command, opts, &block)
+ super(command, opts)
+ @parent_resource = parent_resource
+ @resource = get_interpreter_resource(parent_resource)
+ end
+
+ def evaluate
+ # Add attributes inherited from the parent class
+ # to the resource
+ merge_inherited_attributes
+
+ # Script resources have a code attribute, which is
+ # what is used to execute the command, so include
+ # that with attributes specified by caller in opts
+ block_attributes = @command_opts.merge({:code => @command})
+
+ # Handles cases like powershell_script where default
+ # attributes are different when used in a guard vs. not. For
+ # powershell_script in particular, this will go away when
+ # the one attribue that causes this changes its default to be
+ # the same after some period to prepare for deprecation
+ if @resource.class.respond_to?(:get_default_attributes)
+ block_attributes = @resource.class.send(:get_default_attributes, @command_opts).merge(block_attributes)
+ end
+
+ resource_block = block_from_attributes(block_attributes)
+ evaluate_action(nil, &resource_block)
+ end
+
+ protected
+
+ def evaluate_action(action=nil, &block)
+ @resource.instance_eval(&block)
+
+ run_action = action || @resource.action
+
+ begin
+ @resource.run_action(run_action)
+ resource_updated = @resource.updated
+ rescue Mixlib::ShellOut::ShellCommandFailed
+ resource_updated = nil
+ end
+
+ resource_updated
+ end
+
+ def get_interpreter_resource(parent_resource)
+ if parent_resource.nil? || parent_resource.node.nil?
+ raise ArgumentError, "Node for guard resource parent must not be nil"
+ end
+
+ resource_class = Chef::Resource.resource_for_node(parent_resource.guard_interpreter, parent_resource.node)
+
+ if resource_class.nil?
+ raise ArgumentError, "Specified guard_interpreter resource #{parent_resource.guard_interpreter.to_s} unknown for this platform"
+ end
+
+ if ! resource_class.ancestors.include?(Chef::Resource::Script)
+ raise ArgumentError, "Specified guard interpreter class #{resource_class} must be a kind of Chef::Resource::Script resource"
+ end
+
+ empty_events = Chef::EventDispatch::Dispatcher.new
+ anonymous_run_context = Chef::RunContext.new(parent_resource.node, {}, empty_events)
+ interpreter_resource = resource_class.new('Guard resource', anonymous_run_context)
+
+ interpreter_resource
+ end
+
+ def block_from_attributes(attributes)
+ Proc.new do
+ attributes.keys.each do |attribute_name|
+ send(attribute_name, attributes[attribute_name]) if respond_to?(attribute_name)
+ end
+ end
+ end
+
+ def merge_inherited_attributes
+ inherited_attributes = []
+
+ if @parent_resource.class.respond_to?(:guard_inherited_attributes)
+ inherited_attributes = @parent_resource.class.send(:guard_inherited_attributes)
+ end
+
+ if inherited_attributes && !inherited_attributes.empty?
+ inherited_attributes.each do |attribute|
+ if @parent_resource.respond_to?(attribute) && @resource.respond_to?(attribute)
+ parent_value = @parent_resource.send(attribute)
+ child_value = @resource.send(attribute)
+ if parent_value || child_value
+ @resource.send(attribute, parent_value)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/http.rb b/lib/chef/http.rb
index 78c47735d2..42b5decd6b 100644
--- a/lib/chef/http.rb
+++ b/lib/chef/http.rb
@@ -393,4 +393,3 @@ class Chef
end
end
-
diff --git a/lib/chef/http/decompressor.rb b/lib/chef/http/decompressor.rb
index 78af47798c..e1d776da60 100644
--- a/lib/chef/http/decompressor.rb
+++ b/lib/chef/http/decompressor.rb
@@ -94,16 +94,21 @@ class Chef
# object you can use to unzip/inflate a streaming response.
def stream_response_handler(response)
if gzip_disabled?
+ Chef::Log.debug "disable_gzip is set. \
+ Not using #{response[CONTENT_ENCODING]} \
+ and initializing noop stream deflator."
NoopInflater.new
else
case response[CONTENT_ENCODING]
when GZIP
- Chef::Log.debug "decompressing gzip stream"
+ Chef::Log.debug "Initializing gzip stream deflator"
GzipInflater.new
when DEFLATE
- Chef::Log.debug "decompressing inflate stream"
+ Chef::Log.debug "Initializing deflate stream deflator"
DeflateInflater.new
else
+ Chef::Log.debug "content_encoding = '#{response[CONTENT_ENCODING]}' \
+ initializing noop stream deflator."
NoopInflater.new
end
end
@@ -137,5 +142,3 @@ class Chef
end
end
end
-
-
diff --git a/lib/chef/http/remote_request_id.rb b/lib/chef/http/remote_request_id.rb
new file mode 100644
index 0000000000..6bec5dba4f
--- /dev/null
+++ b/lib/chef/http/remote_request_id.rb
@@ -0,0 +1,46 @@
+# Author:: Prajakta Purohit (<prajakta@opscode.com>)
+# Copyright:: Copyright (c) 2009, 2010, 2013, 2014 Opscode, 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/request_id'
+
+class Chef
+ class HTTP
+ class RemoteRequestID
+
+ def initialize(opts={})
+ end
+
+ def handle_request(method, url, headers={}, data=false)
+ headers.merge!({'X-REMOTE-REQUEST-ID' => Chef::RequestID.instance.request_id})
+ [method, url, headers, data]
+ end
+
+ def handle_response(http_response, rest_request, return_value)
+ [http_response, rest_request, return_value]
+ end
+
+ def stream_response_handler(response)
+ nil
+ end
+
+ def handle_stream_complete(http_response, rest_request, return_value)
+ [http_response, rest_request, return_value]
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/http/simple.rb b/lib/chef/http/simple.rb
index 0ecb28846c..d675a17ee8 100644
--- a/lib/chef/http/simple.rb
+++ b/lib/chef/http/simple.rb
@@ -11,6 +11,11 @@ class Chef
use Decompressor
use CookieManager
+ # ValidateContentLength should come after Decompressor
+ # because the order of middlewares is reversed when handling
+ # responses.
+ use ValidateContentLength
+
end
end
end
diff --git a/lib/chef/http/validate_content_length.rb b/lib/chef/http/validate_content_length.rb
index 49f1738d42..076194e31a 100644
--- a/lib/chef/http/validate_content_length.rb
+++ b/lib/chef/http/validate_content_length.rb
@@ -49,22 +49,20 @@ class Chef
end
def handle_response(http_response, rest_request, return_value)
- unless http_response['content-length']
- Chef::Log.debug("HTTP server did not include a Content-Length header in response, cannot identify truncated downloads.")
- return [http_response, rest_request, return_value]
- end
- validate(response_content_length(http_response), http_response.body.bytesize)
+ validate(http_response, http_response.body.bytesize) if http_response && http_response.body
return [http_response, rest_request, return_value]
end
def handle_stream_complete(http_response, rest_request, return_value)
- if http_response['content-length'].nil?
- Chef::Log.debug("HTTP server did not include a Content-Length header in response, cannot idenfity streamed download.")
- elsif @content_length_counter.nil?
+ if @content_length_counter.nil?
Chef::Log.debug("No content-length information collected for the streamed download, cannot identify streamed download.")
else
- validate(response_content_length(http_response), @content_length_counter.content_length)
+ validate(http_response, @content_length_counter.content_length)
end
+
+ # Make sure the counter is reset since this object might get used
+ # again. See CHEF-5100
+ @content_length_counter = nil
return [http_response, rest_request, return_value]
end
@@ -73,7 +71,9 @@ class Chef
end
private
+
def response_content_length(response)
+ return nil if response['content-length'].nil?
if response['content-length'].is_a?(Array)
response['content-length'].first.to_i
else
@@ -81,12 +81,28 @@ class Chef
end
end
- def validate(content_length, response_length)
- Chef::Log.debug "Content-Length header = #{content_length}"
- Chef::Log.debug "Response body length = #{response_length}"
+ def validate(http_response, response_length)
+ content_length = response_content_length(http_response)
+ transfer_encoding = http_response['transfer-encoding']
+ content_encoding = http_response['content-encoding']
+
+ if content_length.nil?
+ Chef::Log.debug "HTTP server did not include a Content-Length header in response, cannot identify truncated downloads."
+ return true
+ end
+
+ # if Transfer-Encoding is set the RFC states that we must ignore the Content-Length field
+ # CHEF-5041: some proxies uncompress gzip content, leave the incorrect content-length, but set the transfer-encoding field
+ unless transfer_encoding.nil?
+ Chef::Log.debug "Transfer-Encoding header is set, skipping Content-Length check."
+ return true
+ end
+
if response_length != content_length
raise Chef::Exceptions::ContentLengthMismatch.new(response_length, content_length)
end
+
+ Chef::Log.debug "Content-Length validated correctly."
true
end
end
diff --git a/lib/chef/knife.rb b/lib/chef/knife.rb
index eb2c321cab..5cbc968980 100644
--- a/lib/chef/knife.rb
+++ b/lib/chef/knife.rb
@@ -421,6 +421,7 @@ class Chef
# Don't try to load a knife.rb if it wasn't specified.
if config[:config_file]
+ Chef::Config.config_file = config[:config_file]
fetcher = Chef::ConfigFetcher.new(config[:config_file], Chef::Config.config_file_jail)
if fetcher.config_missing?
ui.error("Specified config file #{config[:config_file]} does not exist#{Chef::Config.config_file_jail ? " or is not under config file jail #{Chef::Config.config_file_jail}" : ""}!")
diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb
index 14dccb3892..a7c10fc608 100644
--- a/lib/chef/knife/bootstrap.rb
+++ b/lib/chef/knife/bootstrap.rb
@@ -201,7 +201,7 @@ class Chef
$stdout.sync = true
- ui.info("Bootstrapping Chef on #{ui.color(@node_name, :bold)}")
+ ui.info("Connecting to #{ui.color(@node_name, :bold)}")
begin
knife_ssh.run
diff --git a/lib/chef/knife/bootstrap/README.md b/lib/chef/knife/bootstrap/README.md
new file mode 100644
index 0000000000..13a0fe7ada
--- /dev/null
+++ b/lib/chef/knife/bootstrap/README.md
@@ -0,0 +1,12 @@
+This directory contains bootstrap templates which can be used with the -d flag
+to 'knife bootstrap' to install Chef in different ways. To simplify installation,
+and reduce the matrix of common installation patterns to support, we have
+standardized on the [Omnibus](https://github.com/opscode/omnibus-ruby) built installation
+packages.
+
+The 'chef-full' template downloads a script which is used to determine the correct
+Omnibus package for this system from the [Omnitruck](https://github.com/opscode/opscode-omnitruck) API. All other templates in this directory are deprecated and will be removed
+in the future.
+
+You can still utilize custom bootstrap templates on your system if your installation
+needs are unique. Additional information can be found on the [docs site](http://docs.opscode.com/knife_bootstrap.html#custom-templates). \ No newline at end of file
diff --git a/lib/chef/knife/bootstrap/chef-full.erb b/lib/chef/knife/bootstrap/chef-full.erb
index 24ffca2c69..1d75117b72 100644
--- a/lib/chef/knife/bootstrap/chef-full.erb
+++ b/lib/chef/knife/bootstrap/chef-full.erb
@@ -23,6 +23,7 @@ install_sh="https://www.opscode.com/chef/install.sh"
version_string="-v <%= chef_version %>"
if ! exists /usr/bin/chef-client; then
+ echo "Installing Chef Client..."
if exists wget; then
bash <(wget <%= "--proxy=on " if knife_config[:bootstrap_proxy] %> ${install_sh} -O -) ${version_string}
elif exists curl; then
@@ -66,4 +67,6 @@ cat > /etc/chef/first-boot.json <<'EOP'
<%= first_boot.to_json %>
EOP
+echo "Starting first Chef Client run..."
+
<%= start_chef %>'
diff --git a/lib/chef/knife/client_bulk_delete.rb b/lib/chef/knife/client_bulk_delete.rb
index 8bf2c2f116..f2be772759 100644
--- a/lib/chef/knife/client_bulk_delete.rb
+++ b/lib/chef/knife/client_bulk_delete.rb
@@ -27,6 +27,11 @@ class Chef
require 'chef/json_compat'
end
+ option :delete_validators,
+ :short => "-D",
+ :long => "--delete-validators",
+ :description => "Force deletion of clients if they're validators"
+
banner "knife client bulk delete REGEX (options)"
def run
@@ -38,28 +43,62 @@ class Chef
matcher = /#{name_args[0]}/
clients_to_delete = {}
+ validators_to_delete = {}
all_clients.each do |name, client|
next unless name =~ matcher
- clients_to_delete[client.name] = client
+ if client.validator
+ validators_to_delete[client.name] = client
+ else
+ clients_to_delete[client.name] = client
+ end
end
- if clients_to_delete.empty?
+ if clients_to_delete.empty? && validators_to_delete.empty?
ui.info "No clients match the expression /#{name_args[0]}/"
exit 0
end
- ui.msg("The following clients will be deleted:")
- ui.msg("")
- ui.msg(ui.list(clients_to_delete.keys.sort, :columns_down))
- ui.msg("")
- ui.confirm("Are you sure you want to delete these clients")
+ check_and_delete_validators(validators_to_delete)
+ check_and_delete_clients(clients_to_delete)
+ end
- clients_to_delete.sort.each do |name, client|
+ def check_and_delete_validators(validators)
+ unless validators.empty?
+ unless config[:delete_validators]
+ ui.msg("Following clients are validators and will not be deleted.")
+ print_clients(validators)
+ ui.msg("You must specify --delete-validators to delete the validator clients")
+ else
+ ui.msg("The following validators will be deleted:")
+ print_clients(validators)
+ if ui.confirm_without_exit("Are you sure you want to delete these validators")
+ destroy_clients(validators)
+ end
+ end
+ end
+ end
+
+ def check_and_delete_clients(clients)
+ unless clients.empty?
+ ui.msg("The following clients will be deleted:")
+ print_clients(clients)
+ ui.confirm("Are you sure you want to delete these clients")
+ destroy_clients(clients)
+ end
+ end
+
+ def destroy_clients(clients)
+ clients.sort.each do |name, client|
client.destroy
ui.msg("Deleted client #{name}")
end
end
+
+ def print_clients(clients)
+ ui.msg("")
+ ui.msg(ui.list(clients.keys.sort, :columns_down))
+ ui.msg("")
+ end
end
end
end
-
diff --git a/lib/chef/knife/client_create.rb b/lib/chef/knife/client_create.rb
index 285254aef0..b2bac36081 100644
--- a/lib/chef/knife/client_create.rb
+++ b/lib/chef/knife/client_create.rb
@@ -38,6 +38,11 @@ class Chef
:description => "Create the client as an admin",
:boolean => true
+ option :validator,
+ :long => "--validator",
+ :description => "Create the client as a validator",
+ :boolean => true
+
banner "knife client create CLIENT (options)"
def run
@@ -52,6 +57,7 @@ class Chef
client = Chef::ApiClient.new
client.name(@client_name)
client.admin(config[:admin])
+ client.validator(config[:validator])
output = edit_data(client)
diff --git a/lib/chef/knife/client_delete.rb b/lib/chef/knife/client_delete.rb
index 6a6fae7ea0..1902145c8d 100644
--- a/lib/chef/knife/client_delete.rb
+++ b/lib/chef/knife/client_delete.rb
@@ -27,6 +27,11 @@ class Chef
require 'chef/json_compat'
end
+ option :delete_validators,
+ :short => "-D",
+ :long => "--delete-validators",
+ :description => "Force deletion of client if it's a validator"
+
banner "knife client delete CLIENT (options)"
def run
@@ -38,7 +43,16 @@ class Chef
exit 1
end
- delete_object(Chef::ApiClient, @client_name)
+ delete_object(Chef::ApiClient, @client_name, 'client') {
+ object = Chef::ApiClient.load(@client_name)
+ if object.validator
+ unless config[:delete_validators]
+ ui.fatal("You must specify --force to delete the validator client #{@client_name}")
+ exit 2
+ end
+ end
+ object.destroy
+ }
end
end
diff --git a/lib/chef/knife/cookbook_bulk_delete.rb b/lib/chef/knife/cookbook_bulk_delete.rb
index f8ad74d856..65fa888486 100644
--- a/lib/chef/knife/cookbook_bulk_delete.rb
+++ b/lib/chef/knife/cookbook_bulk_delete.rb
@@ -49,7 +49,7 @@ class Chef
ui.msg ""
unless config[:yes]
- ui.confirm("Do you really want to delete these cookbooks? (Y/N) ", false)
+ ui.confirm("Do you really want to delete these cookbooks")
if config[:purge]
ui.msg("Files that are common to multiple cookbooks are shared, so purging the files may break other cookbooks.")
diff --git a/lib/chef/knife/cookbook_upload.rb b/lib/chef/knife/cookbook_upload.rb
index a882cd7109..9d6e0d438d 100644
--- a/lib/chef/knife/cookbook_upload.rb
+++ b/lib/chef/knife/cookbook_upload.rb
@@ -93,6 +93,7 @@ class Chef
end
assert_environment_valid!
+ warn_about_cookbook_shadowing
version_constraints_to_update = {}
upload_failures = 0
upload_ok = 0
@@ -139,6 +140,7 @@ class Chef
end
end
+
upload_failures += @name_args.length - @cookbooks_to_upload.length
if upload_failures == 0
@@ -199,6 +201,10 @@ class Chef
end
def warn_about_cookbook_shadowing
+ # because cookbooks are lazy-loaded, we have to force the loader
+ # to load the cookbooks the user intends to upload here:
+ cookbooks_to_upload
+
unless cookbook_repo.merged_cookbooks.empty?
ui.warn "* " * 40
ui.warn(<<-WARNING)
@@ -257,14 +263,18 @@ WARNING
end
def check_for_dependencies!(cookbook)
- # for each dependency, check if the version is on the server, or
+ # for all dependencies, check if the version is on the server, or
# the version is in the cookbooks being uploaded. If not, exit and warn the user.
- cookbook.metadata.dependencies.each do |cookbook_name, version|
- unless check_server_side_cookbooks(cookbook_name, version) || check_uploading_cookbooks(cookbook_name, version)
- ui.error "Cookbook #{cookbook.name} depends on cookbook '#{cookbook_name}' version '#{version}',"
- ui.error "which is not currently being uploaded and cannot be found on the server."
- exit 1
- end
+ missing_dependencies = cookbook.metadata.dependencies.reject do |cookbook_name, version|
+ check_server_side_cookbooks(cookbook_name, version) || check_uploading_cookbooks(cookbook_name, version)
+ end
+
+ unless missing_dependencies.empty?
+ missing_cookbook_names = missing_dependencies.map { |cookbook_name, version| "'#{cookbook_name}' version '#{version}'"}
+ ui.error "Cookbook #{cookbook.name} depends on cookbooks which are not currently"
+ ui.error "being uploaded and cannot be found on the server."
+ ui.error "The missing cookbook(s) are: #{missing_cookbook_names.join(', ')}"
+ exit 1
end
end
diff --git a/lib/chef/knife/core/bootstrap_context.rb b/lib/chef/knife/core/bootstrap_context.rb
index e1ad606c80..dc10bbb3d3 100644
--- a/lib/chef/knife/core/bootstrap_context.rb
+++ b/lib/chef/knife/core/bootstrap_context.rb
@@ -62,7 +62,6 @@ class Chef
def config_content
client_rb = <<-CONFIG
-log_level :auto
log_location STDOUT
chef_server_url "#{@chef_config[:chef_server_url]}"
validation_client_name "#{@chef_config[:validation_client_name]}"
@@ -93,6 +92,7 @@ CONFIG
# If the user doesn't have a client path configure, let bash use the PATH for what it was designed for
client_path = @chef_config[:chef_client_path] || 'chef-client'
s = "#{client_path} -j /etc/chef/first-boot.json"
+ s << ' -l debug' if @config[:verbosity] and @config[:verbosity] >= 2
s << " -E #{bootstrap_environment}" if chef_version.to_f != 0.9 # only use the -E option on Chef 0.10+
s
end
diff --git a/lib/chef/knife/core/ui.rb b/lib/chef/knife/core/ui.rb
index dfa8c11644..ff2545cfed 100644
--- a/lib/chef/knife/core/ui.rb
+++ b/lib/chef/knife/core/ui.rb
@@ -205,24 +205,61 @@ class Chef
output(format_for_display(object)) if config[:print_after]
end
- def confirm(question, append_instructions=true)
+ def confirmation_instructions(default_choice)
+ case default_choice
+ when true
+ '? (Y/n)'
+ when false
+ '? (y/N)'
+ else
+ '? (Y/N)'
+ end
+ end
+
+ # See confirm method for argument information
+ def confirm_without_exit(question, append_instructions=true, default_choice=nil)
return true if config[:yes]
stdout.print question
- stdout.print "? (Y/N) " if append_instructions
+ stdout.print confirmation_instructions(default_choice) if append_instructions
+
answer = stdin.readline
answer.chomp!
+
case answer
when "Y", "y"
true
when "N", "n"
self.msg("You said no, so I'm done here.")
- exit 3
+ false
+ when ""
+ unless default_choice.nil?
+ default_choice
+ else
+ self.msg("I have no idea what to do with '#{answer}'")
+ self.msg("Just say Y or N, please.")
+ confirm_without_exit(question, append_instructions, default_choice)
+ end
else
- self.msg("I have no idea what to do with #{answer}")
+ self.msg("I have no idea what to do with '#{answer}'")
self.msg("Just say Y or N, please.")
- confirm(question)
+ confirm_without_exit(question, append_instructions, default_choice)
+ end
+ end
+
+ #
+ # Not the ideal signature for a function but we need to stick with this
+ # for now until we get a chance to break our API in Chef 12.
+ #
+ # question => Question to print before asking for confirmation
+ # append_instructions => Should print '? (Y/N)' as instructions
+ # default_choice => Set to true for 'Y', and false for 'N' as default answer
+ #
+ def confirm(question, append_instructions=true, default_choice=nil)
+ unless confirm_without_exit(question, append_instructions, default_choice)
+ exit 3
end
+ true
end
end
diff --git a/lib/chef/knife/node_run_list_add.rb b/lib/chef/knife/node_run_list_add.rb
index dcd41ae997..519c280400 100644
--- a/lib/chef/knife/node_run_list_add.rb
+++ b/lib/chef/knife/node_run_list_add.rb
@@ -34,6 +34,11 @@ class Chef
:long => "--after ITEM",
:description => "Place the ENTRY in the run list after ITEM"
+ option :before,
+ :short => "-b ITEM",
+ :long => "--before ITEM",
+ :description => "Place the ENTRY in the run list before ITEM"
+
def run
node = Chef::Node.load(@name_args[0])
if @name_args.size > 2
@@ -46,7 +51,18 @@ class Chef
entries = @name_args[1].split(',').map { |e| e.strip }
end
- add_to_run_list(node, entries, config[:after])
+ if config[:after] && config[:before]
+ ui.fatal("You cannot specify both --before and --after!")
+ exit 1
+ end
+
+ if config[:after]
+ add_to_run_list_after(node, entries, config[:after])
+ elsif config[:before]
+ add_to_run_list_before(node, entries, config[:before])
+ else
+ add_to_run_list_after(node, entries)
+ end
node.save
@@ -55,7 +71,9 @@ class Chef
output(format_for_display(node))
end
- def add_to_run_list(node, entries, after=nil)
+ private
+
+ def add_to_run_list_after(node, entries, after=nil)
if after
nlist = []
node.run_list.each do |entry|
@@ -70,6 +88,17 @@ class Chef
end
end
+ def add_to_run_list_before(node, entries, before)
+ nlist = []
+ node.run_list.each do |entry|
+ if entry == before
+ entries.each { |e| nlist << e }
+ end
+ nlist << entry
+ end
+ node.run_list.reset!(nlist)
+ end
+
end
end
end
diff --git a/lib/chef/knife/raw.rb b/lib/chef/knife/raw.rb
index 2756de1a5a..954d46beee 100644
--- a/lib/chef/knife/raw.rb
+++ b/lib/chef/knife/raw.rb
@@ -42,6 +42,7 @@ class Chef
use Chef::HTTP::CookieManager
use Chef::HTTP::Decompressor
use Chef::HTTP::Authenticator
+ use Chef::HTTP::RemoteRequestID
end
def run
diff --git a/lib/chef/knife/ssh.rb b/lib/chef/knife/ssh.rb
index 83c1735b4a..d32b3309ed 100644
--- a/lib/chef/knife/ssh.rb
+++ b/lib/chef/knife/ssh.rb
@@ -114,7 +114,7 @@ class Chef
end
case config[:on_error]
when :skip
- ui.warn "Failed to connect to #{node_name} -- #{$!.class.name}: #{$!.message}"
+ ui.warn "Failed to connect to #{server.host} -- #{$!.class.name}: #{$!.message}"
$!.backtrace.each { |l| Chef::Log.debug(l) }
when :raise
#Net::SSH::Multi magic to force exception to be re-raised.
@@ -142,31 +142,9 @@ class Chef
end
def configure_session
- list = case config[:manual]
- when true
- @name_args[0].split(" ")
- when false
- r = Array.new
- q = Chef::Search::Query.new
- @action_nodes = q.search(:node, @name_args[0])[0]
- @action_nodes.each do |item|
- # we should skip the loop to next iteration if the item returned by the search is nil
- next if item.nil?
- # if a command line attribute was not passed, and we have a cloud public_hostname, use that.
- # see #configure_attribute for the source of config[:attribute] and config[:override_attribute]
- if !config[:override_attribute] && item[:cloud] and item[:cloud][:public_hostname]
- i = item[:cloud][:public_hostname]
- elsif config[:override_attribute]
- i = extract_nested_value(item, config[:override_attribute])
- else
- i = extract_nested_value(item, config[:attribute])
- end
- # next if we couldn't find the specified attribute in the returned node object
- next if i.nil?
- r.push(i)
- end
- r
- end
+ list = config[:manual] ?
+ @name_args[0].split(" ") :
+ search_nodes
if list.length == 0
if @action_nodes.length == 0
ui.fatal("No nodes returned from search!")
@@ -180,21 +158,54 @@ class Chef
session_from_list(list)
end
+ def search_nodes
+ list = Array.new
+ query = Chef::Search::Query.new
+ @action_nodes = query.search(:node, @name_args[0])[0]
+ @action_nodes.each do |item|
+ # we should skip the loop to next iteration if the item
+ # returned by the search is nil
+ next if item.nil?
+ # if a command line attribute was not passed, and we have a
+ # cloud public_hostname, use that. see #configure_attribute
+ # for the source of config[:attribute] and
+ # config[:override_attribute]
+ if config[:override_attribute]
+ host = extract_nested_value(item, config[:override_attribute])
+ elsif item[:cloud] && item[:cloud][:public_hostname]
+ host = item[:cloud][:public_hostname]
+ else
+ host = extract_nested_value(item, config[:attribute])
+ end
+ # next if we couldn't find the specified attribute in the
+ # returned node object
+ next if host.nil?
+ ssh_port = item[:cloud].nil? ? nil : item[:cloud][:public_ssh_port]
+ srv = [host, ssh_port]
+ list.push(srv)
+ end
+ list
+ end
+
def session_from_list(list)
list.each do |item|
- Chef::Log.debug("Adding #{item}")
+ host, ssh_port = item
+ Chef::Log.debug("Adding #{host}")
session_opts = {}
- ssh_config = Net::SSH.configuration_for(item)
+ ssh_config = Net::SSH.configuration_for(host)
# Chef::Config[:knife][:ssh_user] is parsed in #configure_user and written to config[:ssh_user]
user = config[:ssh_user] || ssh_config[:user]
- hostspec = user ? "#{user}@#{item}" : item
+ hostspec = user ? "#{user}@#{host}" : host
session_opts[:keys] = File.expand_path(config[:identity_file]) if config[:identity_file]
session_opts[:keys_only] = true if config[:identity_file]
session_opts[:password] = config[:ssh_password] if config[:ssh_password]
session_opts[:forward_agent] = config[:forward_agent]
- session_opts[:port] = config[:ssh_port] || Chef::Config[:knife][:ssh_port] || ssh_config[:port]
+ session_opts[:port] = config[:ssh_port] ||
+ ssh_port || # Use cloud port if available
+ Chef::Config[:knife][:ssh_port] ||
+ ssh_config[:port]
session_opts[:logger] = Chef::Log.logger if Chef::Log.level == :debug
if !config[:host_key_verify]
@@ -204,7 +215,7 @@ class Chef
session.use(hostspec, session_opts)
- @longest = item.length if item.length > @longest
+ @longest = host.length if host.length > @longest
end
session
@@ -510,6 +521,8 @@ class Chef
end
end
+ private :search_nodes
+
end
end
end
diff --git a/lib/chef/knife/ssl_check.rb b/lib/chef/knife/ssl_check.rb
new file mode 100644
index 0000000000..e98469d5aa
--- /dev/null
+++ b/lib/chef/knife/ssl_check.rb
@@ -0,0 +1,213 @@
+#
+# Author:: Daniel DeLeo (<dan@getchef.com>)
+# Copyright:: Copyright (c) 2014 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'
+require 'chef/config'
+
+class Chef
+ class Knife
+ class SslCheck < Chef::Knife
+
+ deps do
+ require 'pp'
+ require 'socket'
+ require 'uri'
+ require 'chef/http/ssl_policies'
+ require 'openssl'
+ end
+
+ banner "knife ssl check [URL] (options)"
+
+ def initialize(*args)
+ @host = nil
+ @verify_peer_socket = nil
+ @ssl_policy = HTTP::DefaultSSLPolicy
+ super
+ end
+
+ def uri
+ @uri ||= begin
+ Chef::Log.debug("Checking SSL cert on #{given_uri}")
+ URI.parse(given_uri)
+ end
+ end
+
+ def given_uri
+ (name_args[0] or Chef::Config.chef_server_url)
+ end
+
+ def host
+ uri.host
+ end
+
+ def port
+ uri.port
+ end
+
+ def validate_uri
+ unless host && port
+ invalid_uri!
+ end
+ rescue URI::Error
+ invalid_uri!
+ end
+
+ def invalid_uri!
+ ui.error("Given URI: `#{given_uri}' is invalid")
+ show_usage
+ exit 1
+ end
+
+
+ def verify_peer_socket
+ @verify_peer_socket ||= begin
+ tcp_connection = TCPSocket.new(host, port)
+ OpenSSL::SSL::SSLSocket.new(tcp_connection, verify_peer_ssl_context)
+ end
+ end
+
+ def verify_peer_ssl_context
+ @verify_peer_ssl_context ||= begin
+ verify_peer_context = OpenSSL::SSL::SSLContext.new
+ @ssl_policy.apply_to(verify_peer_context)
+ verify_peer_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ verify_peer_context
+ end
+ end
+
+ def noverify_socket
+ @noverify_socket ||= begin
+ tcp_connection = TCPSocket.new(host, port)
+ OpenSSL::SSL::SSLSocket.new(tcp_connection, noverify_peer_ssl_context)
+ end
+ end
+
+ def noverify_peer_ssl_context
+ @noverify_peer_ssl_context ||= begin
+ noverify_peer_context = OpenSSL::SSL::SSLContext.new
+ @ssl_policy.apply_to(noverify_peer_context)
+ noverify_peer_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ noverify_peer_context
+ end
+ end
+
+ def verify_cert
+ ui.msg("Connecting to host #{host}:#{port}")
+ verify_peer_socket.connect
+ true
+ rescue OpenSSL::SSL::SSLError => e
+ ui.error "The SSL certificate of #{host} could not be verified"
+ Chef::Log.debug e.message
+ debug_invalid_cert
+ false
+ end
+
+ def verify_cert_host
+ verify_peer_socket.post_connection_check(host)
+ true
+ rescue OpenSSL::SSL::SSLError => e
+ ui.error "The SSL cert is signed by a trusted authority but is not valid for the given hostname"
+ Chef::Log.debug(e)
+ debug_invalid_host
+ false
+ end
+
+ def debug_invalid_cert
+ noverify_socket.connect
+ issuer_info = noverify_socket.peer_cert.issuer
+ ui.msg("Certificate issuer data: #{issuer_info}")
+
+ ui.msg("\n#{ui.color("Configuration Info:", :bold)}\n\n")
+ debug_ssl_settings
+ debug_chef_ssl_config
+
+ ui.err(<<-ADVICE)
+
+#{ui.color("TO FIX THIS ERROR:", :bold)}
+
+If the server you are connecting to uses a self-signed certificate, you must
+configure chef to trust that server's certificate.
+
+By default, the certificate is stored in the following location on the host
+where your chef-server runs:
+
+ /var/opt/chef-server/nginx/ca/SERVER_HOSTNAME.crt
+
+Copy that file to you trusted_certs_dir (currently: #{configuration.trusted_certs_dir})
+using SSH/SCP or some other secure method, then re-run this command to confirm
+that the server's certificate is now trusted.
+
+ADVICE
+ end
+
+ def debug_invalid_host
+ noverify_socket.connect
+ subject = noverify_socket.peer_cert.subject
+ cn_field_tuple = subject.to_a.find {|field| field[0] == "CN" }
+ cn = cn_field_tuple[1]
+
+ ui.error("You are attempting to connect to: '#{host}'")
+ ui.error("The server's certificate belongs to '#{cn}'")
+ ui.err(<<-ADVICE)
+
+#{ui.color("TO FIX THIS ERROR:", :bold)}
+
+The solution for this issue depends on your networking configuration. If you
+are able to connect to this server using the hostname #{cn}
+instead of #{host}, then you can resolve this issue by updating chef_server_url
+in your configuration file.
+
+If you are not able to connect to the server using the hostname #{cn}
+you will have to update the certificate on the server to use the correct hostname.
+ADVICE
+ end
+
+ def debug_ssl_settings
+ ui.err "OpenSSL Configuration:"
+ ui.err "* Version: #{OpenSSL::OPENSSL_VERSION}"
+ ui.err "* Certificate file: #{OpenSSL::X509::DEFAULT_CERT_FILE}"
+ ui.err "* Certificate directory: #{OpenSSL::X509::DEFAULT_CERT_DIR}"
+ end
+
+ def debug_chef_ssl_config
+ ui.err "Chef SSL Configuration:"
+ ui.err "* ssl_ca_path: #{configuration.ssl_ca_path.inspect}"
+ ui.err "* ssl_ca_file: #{configuration.ssl_ca_file.inspect}"
+ ui.err "* trusted_certs_dir: #{configuration.trusted_certs_dir.inspect}"
+ end
+
+ def configuration
+ Chef::Config
+ end
+
+ def run
+ validate_uri
+ if verify_cert && verify_cert_host
+ ui.msg "Successfully verified certificates from `#{host}'"
+ else
+ exit 1
+ end
+ end
+
+ end
+ end
+end
+
+
+
+
diff --git a/lib/chef/knife/ssl_fetch.rb b/lib/chef/knife/ssl_fetch.rb
new file mode 100644
index 0000000000..5626a5610d
--- /dev/null
+++ b/lib/chef/knife/ssl_fetch.rb
@@ -0,0 +1,145 @@
+#
+# Author:: Daniel DeLeo (<dan@getchef.com>)
+# Copyright:: Copyright (c) 2014 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/ssl_fetch'
+require 'chef/config'
+
+class Chef
+ class Knife
+ class SslFetch < Chef::Knife
+
+ deps do
+ require 'pp'
+ require 'socket'
+ require 'uri'
+ require 'openssl'
+ end
+
+ banner "knife ssl fetch [URL] (options)"
+
+ def initialize(*args)
+ super
+ @uri = nil
+ end
+
+ def uri
+ @uri ||= begin
+ Chef::Log.debug("Checking SSL cert on #{given_uri}")
+ URI.parse(given_uri)
+ end
+ end
+
+ def given_uri
+ (name_args[0] or Chef::Config.chef_server_url)
+ end
+
+ def host
+ uri.host
+ end
+
+ def port
+ uri.port
+ end
+
+ def validate_uri
+ unless host && port
+ invalid_uri!
+ end
+ rescue URI::Error
+ invalid_uri!
+ end
+
+ def invalid_uri!
+ ui.error("Given URI: `#{given_uri}' is invalid")
+ show_usage
+ exit 1
+ end
+
+ def remote_cert_chain
+ tcp_connection = TCPSocket.new(host, port)
+ shady_ssl_connection = OpenSSL::SSL::SSLSocket.new(tcp_connection, noverify_peer_ssl_context)
+ shady_ssl_connection.connect
+ shady_ssl_connection.peer_cert_chain
+ end
+
+ def noverify_peer_ssl_context
+ @noverify_peer_ssl_context ||= begin
+ noverify_peer_context = OpenSSL::SSL::SSLContext.new
+ noverify_peer_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ noverify_peer_context
+ end
+ end
+
+
+ def cn_of(certificate)
+ subject = certificate.subject
+ cn_field_tuple = subject.to_a.find {|field| field[0] == "CN" }
+ cn_field_tuple[1]
+ end
+
+ # Convert the CN of a certificate into something that will work well as a
+ # filename. To do so, all `*` characters are converted to the string
+ # "wildcard" and then all characters other than alphanumeric and hypen
+ # characters are converted to underscores.
+ # NOTE: There is some confustion about what the CN will contain when
+ # using internationalized domain names. RFC 6125 mandates that the ascii
+ # representation be used, but it is not clear whether this is followed in
+ # practice.
+ # https://tools.ietf.org/html/rfc6125#section-6.4.2
+ def normalize_cn(cn)
+ cn.gsub("*", "wildcard").gsub(/[^[:alnum:]\-]/, '_')
+ end
+
+ def configuration
+ Chef::Config
+ end
+
+ def trusted_certs_dir
+ configuration.trusted_certs_dir
+ end
+
+ def write_cert(cert)
+ FileUtils.mkdir_p(trusted_certs_dir)
+ cn = cn_of(cert)
+ filename = File.join(trusted_certs_dir, "#{normalize_cn(cn)}.crt")
+ ui.msg("Adding certificate for #{cn} in #{filename}")
+ File.open(filename, File::CREAT|File::TRUNC|File::RDWR, 0644) do |f|
+ f.print(cert.to_s)
+ end
+ end
+
+ def run
+ validate_uri
+ ui.warn(<<-TRUST_TRUST)
+Certificates from #{host} will be fetched and placed in your trusted_cert
+directory (#{trusted_certs_dir}).
+
+Knife has no means to verify these are the correct certificates. You should
+verify the authenticity of these certificates after downloading.
+
+TRUST_TRUST
+ remote_cert_chain.each do |cert|
+ write_cert(cert)
+ end
+ end
+
+
+ end
+ end
+end
+
diff --git a/lib/chef/mixin/deep_merge.rb b/lib/chef/mixin/deep_merge.rb
index ad3e5803fd..a8a4737758 100644
--- a/lib/chef/mixin/deep_merge.rb
+++ b/lib/chef/mixin/deep_merge.rb
@@ -111,7 +111,13 @@ class Chef
end # deep_merge!
def hash_only_merge(merge_onto, merge_with)
- hash_only_merge!(merge_onto.dup, merge_with.dup)
+ hash_only_merge!(safe_dup(merge_onto), safe_dup(merge_with))
+ end
+
+ def safe_dup(thing)
+ thing.dup
+ rescue TypeError
+ thing
end
# Deep merge without Array merge.
@@ -122,7 +128,11 @@ class Chef
# If there are two Hashes, recursively merge.
if merge_onto.kind_of?(Hash) && merge_with.kind_of?(Hash)
merge_with.each do |key, merge_with_value|
- merge_onto[key] = hash_only_merge!(merge_onto[key], merge_with_value)
+ merge_onto[key] = if merge_onto.has_key?(key)
+ hash_only_merge(merge_onto[key], merge_with_value)
+ else
+ merge_with_value
+ end
end
merge_onto
@@ -158,11 +168,9 @@ class Chef
end
def deep_merge(source, dest)
- deep_merge!(source.dup, dest.dup)
+ deep_merge!(safe_dup(source), safe_dup(dest))
end
end
end
end
-
-
diff --git a/lib/chef/mixin/shell_out.rb b/lib/chef/mixin/shell_out.rb
index f0c2ba2000..56b02d780f 100644
--- a/lib/chef/mixin/shell_out.rb
+++ b/lib/chef/mixin/shell_out.rb
@@ -33,9 +33,7 @@ class Chef
def shell_out(*command_args)
cmd = Mixlib::ShellOut.new(*run_command_compatible_options(command_args))
- if STDOUT.tty? && !Chef::Config[:daemon] && Chef::Log.debug?
- cmd.live_stream = STDOUT
- end
+ cmd.live_stream = io_for_live_stream
cmd.run_command
cmd
end
@@ -73,6 +71,14 @@ class Chef
def deprecate_option(old_option, new_option)
Chef::Log.logger.warn "DEPRECATION: Chef::Mixin::ShellOut option :#{old_option} is deprecated. Use :#{new_option}"
end
+
+ def io_for_live_stream
+ if STDOUT.tty? && !Chef::Config[:daemon] && Chef::Log.debug?
+ STDOUT
+ else
+ nil
+ end
+ end
end
end
end
diff --git a/lib/chef/node.rb b/lib/chef/node.rb
index 69e5e05b01..4992ec2430 100644
--- a/lib/chef/node.rb
+++ b/lib/chef/node.rb
@@ -42,7 +42,7 @@ class Chef
def_delegators :attributes, :keys, :each_key, :each_value, :key?, :has_key?
- attr_accessor :recipe_list, :run_state, :run_list
+ attr_accessor :recipe_list, :run_state, :override_runlist
# RunContext will set itself as run_context via this setter when
# initialized. This is needed so DSL::IncludeAttribute (in particular,
@@ -63,7 +63,8 @@ class Chef
@name = nil
@chef_environment = '_default'
- @run_list = Chef::RunList.new
+ @primary_runlist = Chef::RunList.new
+ @override_runlist = Chef::RunList.new
@attributes = Chef::Node::Attribute.new({}, {}, {}, {})
@@ -259,10 +260,28 @@ class Chef
run_list.include?("role[#{role_name}]")
end
+ def primary_runlist
+ @primary_runlist
+ end
+
+ def override_runlist(*args)
+ args.length > 0 ? @override_runlist.reset!(args) : @override_runlist
+ end
+
+ def select_run_list
+ @override_runlist.empty? ? @primary_runlist : @override_runlist
+ end
+
# Returns an Array of roles and recipes, in the order they will be applied.
# If you call it with arguments, they will become the new list of roles and recipes.
def run_list(*args)
- args.length > 0 ? @run_list.reset!(args) : @run_list
+ rl = select_run_list
+ args.length > 0 ? rl.reset!(args) : rl
+ end
+
+ def run_list=(list)
+ rl = select_run_list
+ rl = list
end
# Returns true if this Node expects a given role, false if not.
@@ -312,7 +331,7 @@ class Chef
if attrs.key?("recipes") || attrs.key?("run_list")
raise Chef::Exceptions::AmbiguousRunlistSpecification, "please set the node's run list using the 'run_list' attribute only."
end
- Chef::Log.info("Setting the run_list to #{new_run_list.inspect} from JSON")
+ Chef::Log.info("Setting the run_list to #{new_run_list.inspect} from CLI options")
run_list(new_run_list)
end
attrs
@@ -410,7 +429,7 @@ class Chef
"default" => attributes.combined_default,
"override" => attributes.combined_override,
#Render correctly for run_list items so malformed json does not result
- "run_list" => run_list.run_list.map { |item| item.to_s }
+ "run_list" => @primary_runlist.run_list.map { |item| item.to_s }
}
result
end
diff --git a/lib/chef/node/attribute_collections.rb b/lib/chef/node/attribute_collections.rb
index d5d496fd60..f09b02b106 100644
--- a/lib/chef/node/attribute_collections.rb
+++ b/lib/chef/node/attribute_collections.rb
@@ -76,8 +76,15 @@ class Chef
super(data)
end
+ # For elements like Fixnums, true, nil...
+ def safe_dup(e)
+ e.dup
+ rescue TypeError
+ e
+ end
+
def dup
- Array.new(map {|e| e.dup})
+ Array.new(map {|e| safe_dup(e)})
end
end
diff --git a/lib/chef/node/immutable_collections.rb b/lib/chef/node/immutable_collections.rb
index f5b3a5121d..3558ba3a86 100644
--- a/lib/chef/node/immutable_collections.rb
+++ b/lib/chef/node/immutable_collections.rb
@@ -85,8 +85,31 @@ class Chef
METHOD_DEFN
end
+ # For elements like Fixnums, true, nil...
+ def safe_dup(e)
+ e.dup
+ rescue TypeError
+ e
+ end
+
def dup
- Array.new(map {|e| e.dup })
+ Array.new(map {|e| safe_dup(e)})
+ end
+
+ def to_a
+ a = Array.new
+ each do |v|
+ a <<
+ case v
+ when ImmutableArray
+ v.to_a
+ when ImmutableMash
+ v.to_hash
+ else
+ v
+ end
+ end
+ a
end
end
@@ -180,6 +203,22 @@ class Chef
Mash.new(self)
end
+ def to_hash
+ h = Hash.new
+ each_pair do |k, v|
+ h[k] =
+ case v
+ when ImmutableMash
+ v.to_hash
+ when ImmutableArray
+ v.to_a
+ else
+ v
+ end
+ end
+ h
+ end
+
end
end
diff --git a/lib/chef/platform/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb
index 92a7278d2f..a773da550e 100644
--- a/lib/chef/platform/provider_mapping.rb
+++ b/lib/chef/platform/provider_mapping.rb
@@ -180,6 +180,7 @@ class Chef
:package => Chef::Provider::Package::Zypper,
:group => Chef::Provider::Group::Suse
},
+ # Only OpenSuSE 12.3+ should use the Usermod group provider:
">= 12.3" => {
:group => Chef::Provider::Group::Usermod
}
@@ -190,19 +191,6 @@ class Chef
:cron => Chef::Provider::Cron,
:package => Chef::Provider::Package::Zypper,
:group => Chef::Provider::Group::Suse
- },
- ###############################################
- # TODO: Remove this after ohai update is released.
- # Only OpenSuSE 12.3+ should use the Usermod group provider:
- # Ohai before OHAI-339 is applied reports both OpenSuSE and SuSE
- # Enterprise as "suse", Ohai after OHAI-339 will report OpenSuSE as
- # "opensuse".
- #
- # In order to support OpenSuSE both before and after the Ohai
- # change, I'm leaving this here. It needs to get removed before
- # SuSE enterprise 12.3 ships.
- ">= 12.3" => {
- :group => Chef::Provider::Group::Usermod
}
},
:oracle => {
@@ -222,6 +210,15 @@ class Chef
:ifconfig => Chef::Provider::Ifconfig::Redhat
}
},
+ :ibm_powerkvm => {
+ :default => {
+ :service => Chef::Provider::Service::Redhat,
+ :cron => Chef::Provider::Cron,
+ :package => Chef::Provider::Package::Yum,
+ :mdadm => Chef::Provider::Mdadm,
+ :ifconfig => Chef::Provider::Ifconfig::Redhat
+ }
+ },
:gentoo => {
:default => {
:package => Chef::Provider::Package::Portage,
@@ -233,7 +230,7 @@ class Chef
:arch => {
:default => {
:package => Chef::Provider::Package::Pacman,
- :service => Chef::Provider::Service::Arch,
+ :service => Chef::Provider::Service::Systemd,
:cron => Chef::Provider::Cron,
:mdadm => Chef::Provider::Mdadm
}
@@ -244,7 +241,9 @@ class Chef
:service => Chef::Provider::Service::Windows,
:user => Chef::Provider::User::Windows,
:group => Chef::Provider::Group::Windows,
- :mount => Chef::Provider::Mount::Windows
+ :mount => Chef::Provider::Mount::Windows,
+ :batch => Chef::Provider::Batch,
+ :powershell_script => Chef::Provider::PowershellScript
}
},
:mingw32 => {
@@ -253,7 +252,9 @@ class Chef
:service => Chef::Provider::Service::Windows,
:user => Chef::Provider::User::Windows,
:group => Chef::Provider::Group::Windows,
- :mount => Chef::Provider::Mount::Windows
+ :mount => Chef::Provider::Mount::Windows,
+ :batch => Chef::Provider::Batch,
+ :powershell_script => Chef::Provider::PowershellScript
}
},
:windows => {
@@ -262,7 +263,9 @@ class Chef
:service => Chef::Provider::Service::Windows,
:user => Chef::Provider::User::Windows,
:group => Chef::Provider::Group::Windows,
- :mount => Chef::Provider::Mount::Windows
+ :mount => Chef::Provider::Mount::Windows,
+ :batch => Chef::Provider::Batch,
+ :powershell_script => Chef::Provider::PowershellScript
}
},
:solaris => {},
@@ -307,7 +310,7 @@ class Chef
:group => Chef::Provider::Group::Usermod,
:user => Chef::Provider::User::Solaris,
},
- ">= 5.9" => {
+ "< 5.11" => {
:service => Chef::Provider::Service::Solaris,
:package => Chef::Provider::Package::Solaris,
:cron => Chef::Provider::Cron::Solaris,
diff --git a/lib/chef/platform/query_helpers.rb b/lib/chef/platform/query_helpers.rb
index 028a220a5d..f9f7af0343 100644
--- a/lib/chef/platform/query_helpers.rb
+++ b/lib/chef/platform/query_helpers.rb
@@ -30,11 +30,19 @@ class Chef
def windows_server_2003?
return false unless windows?
-
require 'ruby-wmi'
+ # CHEF-4888: Work around ruby #2618, expected to be fixed in Ruby 2.1.0
+ # https://github.com/ruby/ruby/commit/588504b20f5cc880ad51827b93e571e32446e5db
+ # https://github.com/ruby/ruby/commit/27ed294c7134c0de582007af3c915a635a6506cd
+ WIN32OLE.ole_initialize
+
host = WMI::Win32_OperatingSystem.find(:first)
- (host.version && host.version.start_with?("5.2"))
+ is_server_2003 = (host.version && host.version.start_with?("5.2"))
+
+ WIN32OLE.ole_uninitialize
+
+ is_server_2003
end
end
diff --git a/lib/chef/policy_builder/expand_node_object.rb b/lib/chef/policy_builder/expand_node_object.rb
index 38b8b7551b..269e722797 100644
--- a/lib/chef/policy_builder/expand_node_object.rb
+++ b/lib/chef/policy_builder/expand_node_object.rb
@@ -40,7 +40,6 @@ class Chef
attr_reader :ohai_data
attr_reader :json_attribs
attr_reader :override_runlist
- attr_reader :original_runlist
attr_reader :run_context
attr_reader :run_list_expansion
@@ -52,7 +51,6 @@ class Chef
@events = events
@node = nil
- @original_runlist = nil
@run_list_expansion = nil
end
@@ -190,7 +188,7 @@ class Chef
# override_runlist was provided. Chef::Client uses this to decide whether
# to do the final node save at the end of the run or not.
def temporary_policy?
- !!@original_runlist
+ !node.override_runlist.empty?
end
########################################
@@ -200,10 +198,9 @@ class Chef
def setup_run_list_override
runlist_override_sanity_check!
unless(override_runlist.empty?)
- @original_runlist = node.run_list.run_list_items.dup
- node.run_list(*override_runlist)
+ node.override_runlist(*override_runlist)
Chef::Log.warn "Run List override has been provided."
- Chef::Log.warn "Original Run List: [#{original_runlist.join(', ')}]"
+ Chef::Log.warn "Original Run List: [#{node.primary_runlist}]"
Chef::Log.warn "Overridden Run List: [#{node.run_list}]"
end
end
diff --git a/lib/chef/provider/cron.rb b/lib/chef/provider/cron.rb
index 87452b4872..1be15f9f5f 100644
--- a/lib/chef/provider/cron.rb
+++ b/lib/chef/provider/cron.rb
@@ -25,11 +25,14 @@ class Chef
class Cron < Chef::Provider
include Chef::Mixin::Command
+ SPECIAL_TIME_VALUES = [:reboot, :yearly, :annually, :monthly, :weekly, :daily, :midnight, :hourly]
+ CRON_ATTRIBUTES = [:minute, :hour, :day, :month, :weekday, :time, :command, :mailto, :path, :shell, :home, :environment]
+ WEEKDAY_SYMBOLS = [:sunday, :monday, :tuesday, :wednesday, :thursday, :friday, :saturday]
+
CRON_PATTERN = /\A([-0-9*,\/]+)\s([-0-9*,\/]+)\s([-0-9*,\/]+)\s([-0-9*,\/]+|[a-zA-Z]{3})\s([-0-9*,\/]+|[a-zA-Z]{3})\s(.*)/
+ SPECIAL_PATTERN = /\A(@(#{SPECIAL_TIME_VALUES.join('|')}))\s(.*)/
ENV_PATTERN = /\A(\S+)=(\S*)/
- CRON_ATTRIBUTES = [:minute, :hour, :day, :month, :weekday, :command, :mailto, :path, :shell, :home, :environment]
-
def initialize(new_resource, run_context)
super(new_resource, run_context)
@cron_exists = false
@@ -58,6 +61,12 @@ class Chef
when ENV_PATTERN
set_environment_var($1, $2) if cron_found
next
+ when SPECIAL_PATTERN
+ if cron_found
+ @current_resource.time($2.to_sym)
+ @current_resource.command($3)
+ cron_found=false
+ end
when CRON_PATTERN
if cron_found
@current_resource.minute($1)
@@ -220,9 +229,22 @@ class Chef
@new_resource.environment.each do |name, value|
newcron << "#{name}=#{value}\n"
end
- newcron << "#{@new_resource.minute} #{@new_resource.hour} #{@new_resource.day} #{@new_resource.month} #{@new_resource.weekday} #{@new_resource.command}\n"
+ if @new_resource.time
+ newcron << "@#{@new_resource.time} #{@new_resource.command}\n"
+ else
+ newcron << "#{@new_resource.minute} #{@new_resource.hour} #{@new_resource.day} #{@new_resource.month} #{@new_resource.weekday} #{@new_resource.command}\n"
+ end
newcron
end
+
+ def weekday_in_crontab
+ weekday_in_crontab = WEEKDAY_SYMBOLS.index(@new_resource.weekday)
+ if weekday_in_crontab.nil?
+ @new_resource.weekday
+ else
+ weekday_in_crontab.to_s
+ end
+ end
end
end
end
diff --git a/lib/chef/provider/deploy.rb b/lib/chef/provider/deploy.rb
index d1017dba62..516aee6159 100644
--- a/lib/chef/provider/deploy.rb
+++ b/lib/chef/provider/deploy.rb
@@ -266,7 +266,7 @@ class Chef
def copy_cached_repo
target_dir_path = @new_resource.deploy_to + "/releases"
- converge_by("deploy from repo to #{@target_dir_path} ") do
+ converge_by("deploy from repo to #{target_dir_path} ") do
FileUtils.rm_rf(release_path) if ::File.exist?(release_path)
FileUtils.mkdir_p(target_dir_path)
FileUtils.cp_r(::File.join(@new_resource.destination, "."), release_path, :preserve => true)
diff --git a/lib/chef/provider/group.rb b/lib/chef/provider/group.rb
index f01677b3ac..35a16c870c 100644
--- a/lib/chef/provider/group.rb
+++ b/lib/chef/provider/group.rb
@@ -84,7 +84,7 @@ class Chef
# <false>:: If a change is not required
def compare_group
@change_desc = [ ]
- if @new_resource.gid != @current_resource.gid
+ if @new_resource.gid.to_s != @current_resource.gid.to_s
@change_desc << "change gid #{@current_resource.gid} to #{@new_resource.gid}"
end
diff --git a/lib/chef/provider/ifconfig/debian.rb b/lib/chef/provider/ifconfig/debian.rb
index 821f4fe924..7589971143 100644
--- a/lib/chef/provider/ifconfig/debian.rb
+++ b/lib/chef/provider/ifconfig/debian.rb
@@ -24,6 +24,9 @@ class Chef
class Ifconfig
class Debian < Chef::Provider::Ifconfig
+ INTERFACES_FILE = "/etc/network/interfaces"
+ INTERFACES_DOT_D_DIR = "/etc/network/interfaces.d"
+
def initialize(new_resource, run_context)
super(new_resource, run_context)
@config_template = %{
@@ -46,22 +49,30 @@ iface <%= @new_resource.device %> inet static
<% end %>
<% end %>
}
- @config_path = "/etc/network/interfaces.d/ifcfg-#{@new_resource.device}"
+ @config_path = "#{INTERFACES_DOT_D_DIR}/ifcfg-#{@new_resource.device}"
end
def generate_config
- check_interfaces_config
+ enforce_interfaces_dot_d_sanity
super
end
protected
- def check_interfaces_config
- converge_by ('modify configuration file : /etc/network/interfaces') do
- Dir.mkdir('/etc/network/interfaces.d') unless ::File.directory?('/etc/network/interfaces.d')
- conf = Chef::Util::FileEdit.new('/etc/network/interfaces')
- conf.insert_line_if_no_match('^\s*source\s+/etc/network/interfaces[.]d/[*]\s*$', 'source /etc/network/interfaces.d/*')
- conf.write_file
+ def enforce_interfaces_dot_d_sanity
+ # create /etc/network/interfaces.d via dir resource (to get reporting, etc)
+ dir = Chef::Resource::Directory.new(INTERFACES_DOT_D_DIR, run_context)
+ dir.run_action(:create)
+ new_resource.updated_by_last_action(true) if dir.updated_by_last_action?
+ # roll our own file_edit resource, this will not get reported until we have a file_edit resource
+ interfaces_dot_d_for_regexp = INTERFACES_DOT_D_DIR.gsub(/\./, '\.') # escape dots for the regexp
+ regexp = %r{^\s*source\s+#{interfaces_dot_d_for_regexp}/\*\s*$}
+ unless ::File.exists?(INTERFACES_FILE) && regexp.match(IO.read(INTERFACES_FILE))
+ converge_by("modifying #{INTERFACES_FILE} to source #{INTERFACES_DOT_D_DIR}") do
+ conf = Chef::Util::FileEdit.new(INTERFACES_FILE)
+ conf.insert_line_if_no_match(regexp, "source #{INTERFACES_DOT_D_DIR}/*")
+ conf.write_file
+ end
end
end
diff --git a/lib/chef/provider/mount/mount.rb b/lib/chef/provider/mount/mount.rb
index 25dfd42725..22d61a9236 100644
--- a/lib/chef/provider/mount/mount.rb
+++ b/lib/chef/provider/mount/mount.rb
@@ -244,7 +244,7 @@ class Chef
# So given a symlink like this:
# /dev/mapper/vgroot-tmp.vol -> /dev/dm-9
# First it will try to match "/dev/mapper/vgroot-tmp.vol". If there is no match it will try matching for "/dev/dm-9".
- "(?:#{Regexp.escape(device_real)}|#{Regexp.escape(::File.readlink(device_real))})"
+ "(?:#{Regexp.escape(device_real)}|#{Regexp.escape(::File.expand_path(::File.readlink(device_real),::File.dirname(device_real)))})"
else
Regexp.escape(device_real)
end
diff --git a/lib/chef/provider/ohai.rb b/lib/chef/provider/ohai.rb
index c686f67450..a6b5ab5daa 100644
--- a/lib/chef/provider/ohai.rb
+++ b/lib/chef/provider/ohai.rb
@@ -33,11 +33,12 @@ class Chef
def action_reload
converge_by("re-run ohai and merge results into node attributes") do
ohai = ::Ohai::System.new
- if @new_resource.plugin
- ohai.require_plugin @new_resource.plugin
- else
- ohai.all_plugins
- end
+
+ # If @new_resource.plugin is nil, ohai will reload all the plugins
+ # Otherwise it will only reload the specified plugin
+ # Note that any changes to plugins, or new plugins placed on
+ # the path are picked up by ohai.
+ ohai.all_plugins @new_resource.plugin
node.automatic_attrs.merge! ohai.data
Chef::Log.info("#{@new_resource} reloaded")
end
diff --git a/lib/chef/provider/package/dpkg.rb b/lib/chef/provider/package/dpkg.rb
index 8ec1ad5878..fb366fb6eb 100644
--- a/lib/chef/provider/package/dpkg.rb
+++ b/lib/chef/provider/package/dpkg.rb
@@ -25,7 +25,8 @@ class Chef
class Provider
class Package
class Dpkg < Chef::Provider::Package::Apt
- DPKG_INFO = /([a-z\d\-\+\.]+)\t([\w\d.~-]+)/
+ # http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version
+ DPKG_INFO = /([a-z\d\-\+\.]+)\t([\w\d.~:-]+)/
DPKG_INSTALLED = /^Status: install ok installed/
DPKG_VERSION = /^Version: (.+)$/
diff --git a/lib/chef/provider/package/windows.rb b/lib/chef/provider/package/windows.rb
new file mode 100644
index 0000000000..be1de0b969
--- /dev/null
+++ b/lib/chef/provider/package/windows.rb
@@ -0,0 +1,80 @@
+#
+# Author:: Bryan McLellan <btm@loftninjas.org>
+# Copyright:: Copyright (c) 2014 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/resource/windows_package'
+require 'chef/provider/package'
+
+class Chef
+ class Provider
+ class Package
+ class Windows < Chef::Provider::Package
+
+ # Depending on the installer, we may need to examine installer_type or
+ # source attributes, or search for text strings in the installer file
+ # binary to determine the installer type for the user. Since the file
+ # must be on disk to do so, we have to make this choice in the provider.
+ require 'chef/provider/package/windows/msi.rb'
+
+ # load_current_resource is run in Chef::Provider#run_action when not in whyrun_mode?
+ def load_current_resource
+ @current_resource = Chef::Resource::WindowsPackage.new(@new_resource.name)
+ @current_resource.version(package_provider.installed_version)
+ @new_resource.version(package_provider.package_version)
+ @current_resource
+ end
+
+ def package_provider
+ @package_provider ||= begin
+ case installer_type
+ when :msi
+ Chef::Provider::Package::Windows::MSI.new(@new_resource)
+ else
+ raise "Unable to find a Chef::Provider::Package::Windows provider for installer_type '#{installer_type}'"
+ end
+ end
+ end
+
+ def installer_type
+ @installer_type ||= begin
+ if @new_resource.installer_type
+ @new_resource.installer_type
+ else
+ file_extension = ::File.basename(@new_resource.source).split(".").last.downcase
+
+ if file_extension == "msi"
+ :msi
+ else
+ raise ArgumentError, "Installer type for Windows Package '#{@new_resource.name}' not specified and cannot be determined from file extension '#{file_extension}'"
+ end
+ end
+ end
+ end
+
+ # Chef::Provider::Package action_install + action_remove call install_package + remove_package
+ # Pass those calls to the correct sub-provider
+ def install_package(name, version)
+ package_provider.install_package(name, version)
+ end
+
+ def remove_package(name, version)
+ package_provider.remove_package(name, version)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/windows/msi.rb b/lib/chef/provider/package/windows/msi.rb
new file mode 100644
index 0000000000..a342600678
--- /dev/null
+++ b/lib/chef/provider/package/windows/msi.rb
@@ -0,0 +1,69 @@
+#
+# Author:: Bryan McLellan <btm@loftninjas.org>
+# Copyright:: Copyright (c) 2014 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.
+#
+
+# TODO: Allow @new_resource.source to be a Product Code as a GUID for uninstall / network install
+
+require 'chef/mixin/shell_out'
+require 'chef/win32/api/installer' if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+
+class Chef
+ class Provider
+ class Package
+ class Windows
+ class MSI
+ include Chef::ReservedNames::Win32::API::Installer if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+ include Chef::Mixin::ShellOut
+
+ def initialize(resource)
+ @new_resource = resource
+ end
+
+ # From Chef::Provider::Package
+ def expand_options(options)
+ options ? " #{options}" : ""
+ end
+
+ # Returns a version if the package is installed or nil if it is not.
+ def installed_version
+ Chef::Log.debug("#{@new_resource} getting product code for package at #{@new_resource.source}")
+ product_code = get_product_property(@new_resource.source, "ProductCode")
+ Chef::Log.debug("#{@new_resource} checking package status and verion for #{product_code}")
+ get_installed_version(product_code)
+ end
+
+ def package_version
+ Chef::Log.debug("#{@new_resource} getting product version for package at #{@new_resource.source}")
+ get_product_property(@new_resource.source, "ProductVersion")
+ end
+
+ def install_package(name, version)
+ # We could use MsiConfigureProduct here, but we'll start off with msiexec
+ Chef::Log.debug("#{@new_resource} installing MSI package '#{@new_resource.source}'")
+ shell_out!("msiexec /qn /i \"#{@new_resource.source}\" #{expand_options(@new_resource.options)}", {:timeout => @new_resource.timeout, :returns => @new_resource.returns})
+ end
+
+ def remove_package(name, version)
+ # We could use MsiConfigureProduct here, but we'll start off with msiexec
+ Chef::Log.debug("#{@new_resource} removing MSI package '#{@new_resource.source}'")
+ shell_out!("msiexec /qn /x \"#{@new_resource.source}\" #{expand_options(@new_resource.options)}", {:timeout => @new_resource.timeout, :returns => @new_resource.returns})
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/powershell_script.rb b/lib/chef/provider/powershell_script.rb
index c459cdf678..967b2d822b 100644
--- a/lib/chef/provider/powershell_script.rb
+++ b/lib/chef/provider/powershell_script.rb
@@ -23,9 +23,9 @@ class Chef
class PowershellScript < Chef::Provider::WindowsScript
protected
-
- EXIT_STATUS_NORMALIZATION_SCRIPT = "\nif ($? -eq $true) {exit 0} elseif ( $LASTEXITCODE -ne 0) {exit $LASTEXITCODE} else { exit 1 }"
- EXIT_STATUS_RESET_SCRIPT = "$LASTEXITCODE=0\n"
+ EXIT_STATUS_EXCEPTION_HANDLER = "\ntrap [Exception] {write-error -exception ($_.Exception.Message);exit 1}".freeze
+ EXIT_STATUS_NORMALIZATION_SCRIPT = "\nif ($? -ne $true) { if ( $LASTEXITCODE -ne 0) {exit $LASTEXITCODE} else { exit 1 }}".freeze
+ EXIT_STATUS_RESET_SCRIPT = "\n$LASTEXITCODE=0".freeze
# Process exit codes are strange with PowerShell. Unless you
# explicitly call exit in Powershell, the powershell.exe
@@ -36,15 +36,28 @@ class Chef
# last process run in the script if it is the last command
# executed, otherwise 0 or 1 based on whether $? is set to true
# (success, where we return 0) or false (where we return 1).
- def NormalizeScriptExitStatus( code )
- @code = (! code.nil?) ? ( EXIT_STATUS_RESET_SCRIPT + code + EXIT_STATUS_NORMALIZATION_SCRIPT ) : nil
+ def normalize_script_exit_status( code )
+ target_code = ( EXIT_STATUS_EXCEPTION_HANDLER +
+ EXIT_STATUS_RESET_SCRIPT +
+ "\n" +
+ code.to_s +
+ EXIT_STATUS_NORMALIZATION_SCRIPT )
+ convert_boolean_return = @new_resource.convert_boolean_return
+ @code = <<EOH
+new-variable -name interpolatedexitcode -visibility private -value $#{convert_boolean_return}
+new-variable -name chefscriptresult -visibility private
+$chefscriptresult = {
+#{target_code}
+}.invokereturnasis()
+if ($interpolatedexitcode -and $chefscriptresult.gettype().name -eq 'boolean') { exit [int32](!$chefscriptresult) } else { exit 0 }
+EOH
end
public
def initialize (new_resource, run_context)
super(new_resource, run_context, '.ps1')
- NormalizeScriptExitStatus(new_resource.code)
+ normalize_script_exit_status(new_resource.code)
end
def flags
diff --git a/lib/chef/provider/service/macosx.rb b/lib/chef/provider/service/macosx.rb
index 4f2de2ccbf..ca78c2eaee 100644
--- a/lib/chef/provider/service/macosx.rb
+++ b/lib/chef/provider/service/macosx.rb
@@ -17,6 +17,7 @@
#
require 'chef/provider/service'
+require 'rexml/document'
class Chef
class Provider
@@ -41,6 +42,7 @@ class Chef
@current_resource.service_name(@new_resource.service_name)
@plist_size = 0
@plist = find_service_plist
+ @service_label = find_service_label
set_service_status
@current_resource
@@ -48,14 +50,6 @@ class Chef
def define_resource_requirements
#super
- requirements.assert(:enable) do |a|
- a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :enable"
- end
-
- requirements.assert(:disable) do |a|
- a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :disable"
- end
-
requirements.assert(:reload) do |a|
a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :reload"
end
@@ -66,6 +60,12 @@ class Chef
end
requirements.assert(:all_actions) do |a|
+ a.assertion { !@service_label.to_s.empty? }
+ a.failure_message Chef::Exceptions::Service,
+ "Could not find service's label in plist file '#{@plist}'!"
+ end
+
+ requirements.assert(:all_actions) do |a|
a.assertion { @plist_size > 0 }
# No failrue here in original code - so we also will not
# fail. Instead warn that the service is potentially missing
@@ -74,7 +74,6 @@ class Chef
@current_resource.running(false)
end
end
-
end
def start_service
@@ -111,19 +110,56 @@ class Chef
end
end
+ # On OS/X, enabling a service has the side-effect of starting it,
+ # and disabling a service has the side-effect of stopping it.
+ #
+ # This makes some sense on OS/X since launchctl is an "init"-style
+ # supervisor that will restart daemons that are crashing, etc.
+ def enable_service
+ if @current_resource.enabled
+ Chef::Log.debug("#{@new_resource} already enabled, not enabling")
+ else
+ shell_out!(
+ "launchctl load -w '#{@plist}'",
+ :user => @owner_uid, :group => @owner_gid
+ )
+ end
+ end
+
+ def disable_service
+ unless @current_resource.enabled
+ Chef::Log.debug("#{@new_resource} not enabled, not disabling")
+ else
+ shell_out!(
+ "launchctl unload -w '#{@plist}'",
+ :user => @owner_uid, :group => @owner_gid
+ )
+ end
+ end
def set_service_status
- return if @plist == nil
+ return if @plist == nil or @service_label.to_s.empty?
- @current_resource.enabled(!@plist.nil?)
+ cmd = shell_out(
+ "launchctl list #{@service_label}",
+ :user => @owner_uid, :group => @owner_gid
+ )
+
+ if cmd.exitstatus == 0
+ @current_resource.enabled(true)
+ else
+ @current_resource.enabled(false)
+ end
if @current_resource.enabled
@owner_uid = ::File.stat(@plist).uid
@owner_gid = ::File.stat(@plist).gid
- shell_out!("launchctl list", :user => @owner_uid, :group => @owner_gid).stdout.each_line do |line|
+ shell_out!(
+ "launchctl list", :user => @owner_uid, :group => @owner_gid
+ ).stdout.each_line do |line|
case line
- when /(\d+|-)\s+(?:\d+|-)\s+(.*\.?)#{@current_resource.service_name}/
+ when /(\d+|-)\s+(?:\d+|-)\s+(.*\.?)#{@service_label}/
pid = $1
@current_resource.running(!pid.to_i.zero?)
end
@@ -135,9 +171,27 @@ class Chef
private
+ def find_service_label
+ # Most services have the same internal label as the name of the
+ # plist file. However, there is no rule saying that *has* to be
+ # the case, and some core services (notably, ssh) do not follow
+ # this rule.
+
+ # plist files can come in XML or Binary formats. this command
+ # will make sure we get XML every time.
+ plist_xml = shell_out!("plutil -convert xml1 -o - #{@plist}").stdout
+
+ plist_doc = REXML::Document.new(plist_xml)
+ plist_doc.elements[
+ "/plist/dict/key[text()='Label']/following::string[1]/text()"]
+ end
+
def find_service_plist
plists = PLIST_DIRS.inject([]) do |results, dir|
- entries = Dir.glob("#{::File.expand_path(dir)}/*#{@current_resource.service_name}*.plist")
+ edir = ::File.expand_path(dir)
+ entries = Dir.glob(
+ "#{edir}/*#{@current_resource.service_name}*.plist"
+ )
entries.any? ? results << entries : results
end
plists.flatten!
diff --git a/lib/chef/provider/service/solaris.rb b/lib/chef/provider/service/solaris.rb
index 4bdb6fbfd1..7f06ac561b 100644
--- a/lib/chef/provider/service/solaris.rb
+++ b/lib/chef/provider/service/solaris.rb
@@ -25,11 +25,13 @@ class Chef
class Service
class Solaris < Chef::Provider::Service
include Chef::Mixin::ShellOut
+ attr_reader :maintenance
def initialize(new_resource, run_context=nil)
super
@init_command = "/usr/sbin/svcadm"
@status_command = "/bin/svcs -l"
+ @maintenace = false
end
@@ -44,6 +46,7 @@ class Chef
end
def enable_service
+ shell_out!("#{default_init_command} clear #{@new_resource.service_name}") if @maintenance
shell_out!("#{default_init_command} enable -s #{@new_resource.service_name}")
end
@@ -65,13 +68,14 @@ class Chef
end
def service_status
- status = popen4("#{@status_command} #{@current_resource.service_name}") do |pid, stdin, stdout, stderr|
- stdout.each do |line|
- case line
- when /state\s+online/
- @current_resource.enabled(true)
- @current_resource.running(true)
- end
+ status = shell_out!("#{@status_command} #{@current_resource.service_name}")
+ status.stdout.each_line do |line|
+ case line
+ when /state\s+online/
+ @current_resource.enabled(true)
+ @current_resource.running(true)
+ when /state\s+maintenance/
+ @maintenance = true
end
end
unless @current_resource.enabled
diff --git a/lib/chef/recipe.rb b/lib/chef/recipe.rb
index 0c688cb5f8..5b95d80590 100644
--- a/lib/chef/recipe.rb
+++ b/lib/chef/recipe.rb
@@ -23,6 +23,7 @@ require 'chef/dsl/data_query'
require 'chef/dsl/platform_introspection'
require 'chef/dsl/include_recipe'
require 'chef/dsl/registry_helper'
+require 'chef/dsl/reboot_pending'
require 'chef/mixin/from_file'
@@ -38,6 +39,7 @@ class Chef
include Chef::DSL::IncludeRecipe
include Chef::DSL::Recipe
include Chef::DSL::RegistryHelper
+ include Chef::DSL::RebootPending
include Chef::Mixin::FromFile
include Chef::Mixin::Deprecation
diff --git a/lib/chef/request_id.rb b/lib/chef/request_id.rb
new file mode 100644
index 0000000000..7fc177c633
--- /dev/null
+++ b/lib/chef/request_id.rb
@@ -0,0 +1,37 @@
+# Author:: Prajakta Purohit (<prajakta@opscode.com>)
+# Copyright:: Copyright (c) 2009, 2010, 2013, 2014 Opscode, 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/monkey_patches/securerandom'
+require 'singleton'
+
+class Chef
+ class RequestID
+ include Singleton
+
+ def reset_request_id
+ @request_id = nil
+ end
+
+ def request_id
+ @request_id ||= generate_request_id
+ end
+
+ def generate_request_id
+ SecureRandom.uuid
+ end
+ end
+end
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
index 997c614171..7d96b26b4b 100644
--- a/lib/chef/resource.rb
+++ b/lib/chef/resource.rb
@@ -21,7 +21,9 @@ require 'chef/mixin/params_validate'
require 'chef/dsl/platform_introspection'
require 'chef/dsl/data_query'
require 'chef/dsl/registry_helper'
+require 'chef/dsl/reboot_pending'
require 'chef/mixin/convert_to_class_name'
+require 'chef//guard_interpreter/resource_guard_interpreter'
require 'chef/resource/conditional'
require 'chef/resource/conditional_action_not_nothing'
require 'chef/resource_collection'
@@ -125,6 +127,7 @@ F
include Chef::Mixin::ParamsValidate
include Chef::DSL::PlatformIntrospection
include Chef::DSL::RegistryHelper
+ include Chef::DSL::RebootPending
include Chef::Mixin::ConvertToClassName
include Chef::Mixin::Deprecation
@@ -247,6 +250,7 @@ F
@not_if = []
@only_if = []
@source_line = nil
+ @guard_interpreter = :default
@elapsed_time = 0
@node = run_context ? deprecated_ivar(run_context.node, :node, :warn) : nil
@@ -399,6 +403,14 @@ F
ignore_failure(arg)
end
+ def guard_interpreter(arg=nil)
+ set_or_return(
+ :guard_interpreter,
+ arg,
+ :kind_of => Symbol
+ )
+ end
+
# Sets up a notification from this resource to the resource specified by +resource_spec+.
def notifies(action, resource_spec, timing=:delayed)
# when using old-style resources(:template => "/foo.txt") style, you
@@ -550,7 +562,7 @@ F
# * evaluates to false if the block is false, or if the command returns a non-zero exit code.
def only_if(command=nil, opts={}, &block)
if command || block_given?
- @only_if << Conditional.only_if(command, opts, &block)
+ @only_if << Conditional.only_if(self, command, opts, &block)
end
@only_if
end
@@ -571,7 +583,7 @@ F
# * evaluates to false if the block is true, or if the command returns a 0 exit status.
def not_if(command=nil, opts={}, &block)
if command || block_given?
- @not_if << Conditional.not_if(command, opts, &block)
+ @not_if << Conditional.not_if(self, command, opts, &block)
end
@not_if
end
@@ -625,7 +637,7 @@ F
provider_for_action(action).run_action
rescue Exception => e
if ignore_failure
- Chef::Log.error("#{self} (#{defined_at}) had an error: #{e.message}; ignore_failure is set, continuing")
+ Chef::Log.error("#{custom_exception_message(e)}; ignore_failure is set, continuing")
events.resource_failed(self, action, e)
elsif retries > 0
events.resource_failed_retriable(self, action, retries, e)
@@ -660,8 +672,12 @@ F
end
end
+ def custom_exception_message(e)
+ "#{self} (#{defined_at}) had an error: #{e.class.name}: #{e.message}"
+ end
+
def customize_exception(e)
- new_exception = e.exception("#{self} (#{defined_at}) had an error: #{e.class.name}: #{e.message}")
+ new_exception = e.exception(custom_exception_message(e))
new_exception.set_backtrace(e.backtrace)
new_exception
end
@@ -813,6 +829,5 @@ F
end
end
end
-
end
end
diff --git a/lib/chef/resource/conditional.rb b/lib/chef/resource/conditional.rb
index 60f65e14e2..e6623be5dd 100644
--- a/lib/chef/resource/conditional.rb
+++ b/lib/chef/resource/conditional.rb
@@ -17,6 +17,7 @@
#
require 'chef/mixin/shell_out'
+require 'chef/guard_interpreter/resource_guard_interpreter'
class Chef
class Resource
@@ -29,12 +30,12 @@ class Chef
private :new
end
- def self.not_if(command=nil, command_opts={}, &block)
- new(:not_if, command, command_opts, &block)
+ def self.not_if(parent_resource, command=nil, command_opts={}, &block)
+ new(:not_if, parent_resource, command, command_opts, &block)
end
- def self.only_if(command=nil, command_opts={}, &block)
- new(:only_if, command, command_opts, &block)
+ def self.only_if(parent_resource, command=nil, command_opts={}, &block)
+ new(:only_if, parent_resource, command, command_opts, &block)
end
attr_reader :positivity
@@ -42,14 +43,16 @@ class Chef
attr_reader :command_opts
attr_reader :block
- def initialize(positivity, command=nil, command_opts={}, &block)
+ def initialize(positivity, parent_resource, command=nil, command_opts={}, &block)
@positivity = positivity
case command
when String
+ @guard_interpreter = new_guard_interpreter(parent_resource, command, command_opts, &block)
@command, @command_opts = command, command_opts
@block = nil
when nil
raise ArgumentError, "only_if/not_if requires either a command or a block" unless block_given?
+ @guard_interpreter = nil
@command, @command_opts = nil, nil
@block = block
else
@@ -69,11 +72,11 @@ class Chef
end
def evaluate
- @command ? evaluate_command : evaluate_block
+ @guard_interpreter ? evaluate_command : evaluate_block
end
def evaluate_command
- shell_out(@command, @command_opts).status.success?
+ @guard_interpreter.evaluate
rescue Chef::Exceptions::CommandTimeout
Chef::Log.warn "Command '#{@command}' timed out"
false
@@ -100,6 +103,16 @@ class Chef
end
end
+ private
+
+ def new_guard_interpreter(parent_resource, command, opts)
+ if parent_resource.guard_interpreter == :default
+ guard_interpreter = Chef::GuardInterpreter::DefaultGuardInterpreter.new(command, opts)
+ else
+ guard_interpreter = Chef::GuardInterpreter::ResourceGuardInterpreter.new(parent_resource, command, opts)
+ end
+ end
+
end
end
end
diff --git a/lib/chef/resource/cron.rb b/lib/chef/resource/cron.rb
index dfbb91f80c..9c04658bf3 100644
--- a/lib/chef/resource/cron.rb
+++ b/lib/chef/resource/cron.rb
@@ -43,6 +43,7 @@ class Chef
@path = nil
@shell = nil
@home = nil
+ @time = nil
@environment = {}
end
@@ -121,13 +122,28 @@ class Chef
converted_arg = arg
end
begin
- if integerize(arg) > 7 then raise RangeError end
+ error_message = "You provided '#{arg}' as a weekday, acceptable values are "
+ error_message << Provider::Cron::WEEKDAY_SYMBOLS.map {|sym| ":#{sym.to_s}"}.join(', ')
+ error_message << " and a string in crontab format"
+ if (arg.is_a?(Symbol) && !Provider::Cron::WEEKDAY_SYMBOLS.include?(arg)) ||
+ (!arg.is_a?(Symbol) && integerize(arg) > 7) ||
+ (!arg.is_a?(Symbol) && integerize(arg) < 0)
+ raise RangeError, error_message
+ end
rescue ArgumentError
end
set_or_return(
:weekday,
converted_arg,
- :kind_of => String
+ :kind_of => [String, Symbol]
+ )
+ end
+
+ def time(arg=nil)
+ set_or_return(
+ :time,
+ arg,
+ :equal_to => Chef::Provider::Cron::SPECIAL_TIME_VALUES
)
end
diff --git a/lib/chef/resource/execute.rb b/lib/chef/resource/execute.rb
index 6c07bf9352..7c4fa48c0a 100644
--- a/lib/chef/resource/execute.rb
+++ b/lib/chef/resource/execute.rb
@@ -125,8 +125,6 @@ class Chef
)
end
-
-
end
end
end
diff --git a/lib/chef/resource/powershell_script.rb b/lib/chef/resource/powershell_script.rb
index cbd81b1259..1b47e7411a 100644
--- a/lib/chef/resource/powershell_script.rb
+++ b/lib/chef/resource/powershell_script.rb
@@ -15,17 +15,39 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-
require 'chef/resource/windows_script'
class Chef
class Resource
class PowershellScript < Chef::Resource::WindowsScript
+ set_guard_inherited_attributes(:architecture)
+
def initialize(name, run_context=nil)
super(name, run_context, :powershell_script, "powershell.exe")
+ @convert_boolean_return = false
+ end
+
+ def convert_boolean_return(arg=nil)
+ set_or_return(
+ :convert_boolean_return,
+ arg,
+ :kind_of => [ FalseClass, TrueClass ]
+ )
end
+ protected
+
+ # Allow callers evaluating guards to request default
+ # attribute values. This is needed to allow
+ # convert_boolean_return to be true in guard context by default,
+ # and false by default otherwise. When this mode becomes the
+ # default for this resource, this method can be removed since
+ # guard context and recipe resource context will have the
+ # same behavior.
+ def self.get_default_attributes(opts)
+ {:convert_boolean_return => true}
+ end
end
end
end
diff --git a/lib/chef/resource/script.rb b/lib/chef/resource/script.rb
index 8cc9c6f0c5..6f66fb9094 100644
--- a/lib/chef/resource/script.rb
+++ b/lib/chef/resource/script.rb
@@ -58,6 +58,31 @@ class Chef
)
end
+ def self.set_guard_inherited_attributes(*inherited_attributes)
+ @class_inherited_attributes = inherited_attributes
+ end
+
+ def self.guard_inherited_attributes(*inherited_attributes)
+ # Similar to patterns elsewhere, return attributes from this
+ # class and superclasses as a form of inheritance
+ ancestor_attributes = []
+
+ if superclass.respond_to?(:guard_inherited_attributes)
+ ancestor_attributes = superclass.guard_inherited_attributes
+ end
+
+ ancestor_attributes.concat(@class_inherited_attributes ? @class_inherited_attributes : []).uniq
+ end
+
+ set_guard_inherited_attributes(
+ :cwd,
+ :environment,
+ :group,
+ :path,
+ :user,
+ :umask
+ )
+
end
end
end
diff --git a/lib/chef/resource/subversion.rb b/lib/chef/resource/subversion.rb
index 04fec9b1d8..44158cb080 100644
--- a/lib/chef/resource/subversion.rb
+++ b/lib/chef/resource/subversion.rb
@@ -32,6 +32,10 @@ class Chef
allowed_actions << :force_export
end
+ # Override exception to strip password if any, so it won't appear in logs and different Chef notifications
+ def custom_exception_message(e)
+ "#{self} (#{defined_at}) had an error: #{e.class.name}: #{svn_password ? e.message.gsub(svn_password, "[hidden_password]") : e.message}"
+ end
end
end
end
diff --git a/lib/chef/resource/windows_package.rb b/lib/chef/resource/windows_package.rb
new file mode 100644
index 0000000000..8bd41e0cb7
--- /dev/null
+++ b/lib/chef/resource/windows_package.rb
@@ -0,0 +1,79 @@
+#
+# Author:: Bryan McLellan <btm@loftninjas.org>
+# Copyright:: Copyright (c) 2014 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/resource/package'
+require 'chef/provider/package/windows'
+require 'chef/win32/error' if RUBY_PLATFORM =~ /mswin|mingw|windows/
+
+class Chef
+ class Resource
+ class WindowsPackage < Chef::Resource::Package
+
+ provides :package, :on_platforms => ["windows"]
+
+ def initialize(name, run_context=nil)
+ super
+ @allowed_actions = [ :install, :remove ]
+ @provider = Chef::Provider::Package::Windows
+ @resource_name = :windows_package
+ @source ||= source(@package_name)
+
+ # Unique to this resource
+ @installer_type = nil
+ @timeout = 600
+ # In the past we accepted return code 127 for an unknown reason and 42 because of a bug
+ @returns = [ 0 ]
+ end
+
+ def installer_type(arg=nil)
+ set_or_return(
+ :installer_type,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ def timeout(arg=nil)
+ set_or_return(
+ :timeout,
+ arg,
+ :kind_of => [ String, Integer ]
+ )
+ end
+
+ def returns(arg=nil)
+ set_or_return(
+ :returns,
+ arg,
+ :kind_of => [ String, Integer, Array ]
+ )
+ end
+
+ def source(arg=nil)
+ if arg == nil && self.instance_variable_defined?(:@source) == true
+ @source
+ else
+ raise ArgumentError, "Bad type for WindowsPackage resource, use a String" unless arg.is_a?(String)
+ Chef::Log.debug("#{package_name}: sanitizing source path '#{arg}'")
+ @source = ::File.absolute_path(arg).gsub(::File::SEPARATOR, ::File::ALT_SEPARATOR)
+ end
+ end
+ end
+ end
+end
+
diff --git a/lib/chef/resource/windows_script.rb b/lib/chef/resource/windows_script.rb
index 2b563f5bec..108891e9ba 100644
--- a/lib/chef/resource/windows_script.rb
+++ b/lib/chef/resource/windows_script.rb
@@ -52,11 +52,6 @@ class Chef
"cannot execute script with requested architecture '#{desired_architecture.to_s}' on a system with architecture '#{node_windows_architecture(node)}'"
end
end
-
- def node
- run_context && run_context.node
- end
-
end
end
end
diff --git a/lib/chef/resource_reporter.rb b/lib/chef/resource_reporter.rb
index 04f4ee26de..d191710cb4 100644
--- a/lib/chef/resource_reporter.rb
+++ b/lib/chef/resource_reporter.rb
@@ -107,7 +107,6 @@ class Chef
@pending_update = nil
@status = "success"
@exception = nil
- @run_id = SecureRandom.uuid
@rest_client = rest_client
@error_descriptions = {}
end
@@ -118,7 +117,7 @@ class Chef
if reporting_enabled?
begin
resource_history_url = "reports/nodes/#{node_name}/runs"
- server_response = @rest_client.post_rest(resource_history_url, {:action => :start, :run_id => @run_id,
+ server_response = @rest_client.post_rest(resource_history_url, {:action => :start, :run_id => run_id,
:start_time => start_time.to_s}, headers)
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
handle_error_starting_run(e, resource_history_url)
@@ -158,6 +157,10 @@ class Chef
@reporting_enabled = false
end
+ def run_id
+ @run_status.run_id
+ end
+
def resource_current_state_loaded(new_resource, action, current_resource)
unless nested_resource?(new_resource)
@pending_update = ResourceReport.new_with_current_state(new_resource, action, current_resource)
@@ -214,8 +217,8 @@ class Chef
def post_reporting_data
if reporting_enabled?
run_data = prepare_run_data
- resource_history_url = "reports/nodes/#{node_name}/runs/#{@run_id}"
- Chef::Log.info("Sending resource update report (run-id: #{@run_id})")
+ resource_history_url = "reports/nodes/#{node_name}/runs/#{run_id}"
+ Chef::Log.info("Sending resource update report (run-id: #{run_id})")
Chef::Log.debug run_data.inspect
compressed_data = encode_gzip(run_data.to_json)
begin
diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb
index 76adb6f1e1..711becef8c 100644
--- a/lib/chef/resources.rb
+++ b/lib/chef/resources.rb
@@ -69,6 +69,7 @@ require 'chef/resource/template'
require 'chef/resource/timestamped_deploy'
require 'chef/resource/user'
require 'chef/resource/whyrun_safe_ruby_block'
+require 'chef/resource/windows_package'
require 'chef/resource/yum_package'
require 'chef/resource/lwrp_base'
require 'chef/resource/bff_package'
diff --git a/lib/chef/rest.rb b/lib/chef/rest.rb
index a1139d7fa2..f0de443058 100644
--- a/lib/chef/rest.rb
+++ b/lib/chef/rest.rb
@@ -36,6 +36,7 @@ require 'chef/http/validate_content_length'
require 'chef/config'
require 'chef/exceptions'
require 'chef/platform/query_helpers'
+require 'chef/http/remote_request_id'
class Chef
# == Chef::REST
@@ -56,19 +57,27 @@ class Chef
# http://localhost:4000, a call to +get_rest+ with 'nodes' will make an
# HTTP GET request to http://localhost:4000/nodes
def initialize(url, client_name=Chef::Config[:node_name], signing_key_filename=Chef::Config[:client_key], options={})
+ options = options.dup
options[:client_name] = client_name
options[:signing_key_filename] = signing_key_filename
super(url, options)
@decompressor = Decompressor.new(options)
@authenticator = Authenticator.new(options)
+ @request_id = RemoteRequestID.new(options)
- @middlewares << ValidateContentLength.new(options)
@middlewares << JSONInput.new(options)
@middlewares << JSONToModelOutput.new(options)
@middlewares << CookieManager.new(options)
@middlewares << @decompressor
@middlewares << @authenticator
+ @middlewares << @request_id
+
+ # ValidateContentLength should come after Decompressor
+ # because the order of middlewares is reversed when handling
+ # responses.
+ @middlewares << ValidateContentLength.new(options)
+
end
def signing_key_filename
@@ -132,7 +141,7 @@ class Chef
def raw_http_request(method, path, headers, data)
url = create_url(path)
method, url, headers, data = @authenticator.handle_request(method, url, headers, data)
-
+ method, url, headers, data = @request_id.handle_request(method, url, headers, data)
response, rest_request, return_value = send_http_request(method, url, headers, data)
response.error! unless success_response?(response)
return_value
diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb
index 05a954ad15..a102ef4692 100644
--- a/lib/chef/run_context.rb
+++ b/lib/chef/run_context.rb
@@ -77,13 +77,15 @@ class Chef
@events = events
@node.run_context = self
+
+ @cookbook_compiler = nil
end
# Triggers the compile phase of the chef run. Implemented by
# Chef::RunContext::CookbookCompiler
def load(run_list_expansion)
- compiler = CookbookCompiler.new(self, run_list_expansion, events)
- compiler.compile
+ @cookbook_compiler = CookbookCompiler.new(self, run_list_expansion, events)
+ @cookbook_compiler.compile
end
# Adds an immediate notification to the
@@ -141,6 +143,18 @@ class Chef
Chef::Log.debug("Loading Recipe #{recipe_name} via include_recipe")
cookbook_name, recipe_short_name = Chef::Recipe.parse_recipe_name(recipe_name)
+
+ if unreachable_cookbook?(cookbook_name) # CHEF-4367
+ Chef::Log.warn(<<-ERROR_MESSAGE)
+MissingCookbookDependency:
+Recipe `#{recipe_name}` is not in the run_list, and cookbook '#{cookbook_name}'
+is not a dependency of any cookbook in the run_list. To load this recipe,
+first add a dependency on cookbook '#{cookbook_name}' in the cookbook you're
+including it from in that cookbook's metadata.
+ERROR_MESSAGE
+ end
+
+
if loaded_fully_qualified_recipe?(cookbook_name, recipe_short_name)
Chef::Log.debug("I am not loading #{recipe_name}, because I have already seen it.")
false
@@ -228,6 +242,12 @@ class Chef
cookbook.has_cookbook_file_for_node?(node, cb_file_name)
end
+ # Delegates to CookbookCompiler#unreachable_cookbook?
+ # Used to raise an error when attempting to load a recipe belonging to a
+ # cookbook that is not in the dependency graph. See also: CHEF-4367
+ def unreachable_cookbook?(cookbook_name)
+ @cookbook_compiler.unreachable_cookbook?(cookbook_name)
+ end
private
diff --git a/lib/chef/run_context/cookbook_compiler.rb b/lib/chef/run_context/cookbook_compiler.rb
index 0a05061152..abe5afa7ae 100644
--- a/lib/chef/run_context/cookbook_compiler.rb
+++ b/lib/chef/run_context/cookbook_compiler.rb
@@ -16,6 +16,7 @@
# limitations under the License.
#
+require 'set'
require 'chef/log'
require 'chef/recipe'
require 'chef/resource/lwrp_base'
@@ -149,6 +150,17 @@ class Chef
@events.recipe_load_complete
end
+ # Whether or not a cookbook is reachable from the set of cookbook given
+ # by the run_list plus those cookbooks' dependencies.
+ def unreachable_cookbook?(cookbook_name)
+ !reachable_cookbooks.include?(cookbook_name)
+ end
+
+ # All cookbooks in the dependency graph, returned as a Set.
+ def reachable_cookbooks
+ @reachable_cookbooks ||= Set.new(cookbook_order)
+ end
+
private
def load_attributes_from_cookbook(cookbook_name)
diff --git a/lib/chef/run_status.rb b/lib/chef/run_status.rb
index 9354f7872a..0f181426b0 100644
--- a/lib/chef/run_status.rb
+++ b/lib/chef/run_status.rb
@@ -37,6 +37,8 @@ class Chef::RunStatus
attr_writer :exception
+ attr_accessor :run_id
+
def initialize(node, events)
@node = node
@events = events
@@ -112,7 +114,8 @@ class Chef::RunStatus
:all_resources => all_resources,
:updated_resources => updated_resources,
:exception => formatted_exception,
- :backtrace => backtrace}
+ :backtrace => backtrace,
+ :run_id => run_id}
end
# Returns a string of the format "ExceptionClass: message" or +nil+ if no
diff --git a/lib/chef/server_api.rb b/lib/chef/server_api.rb
index e9e7593dd6..8cdcd7a09d 100644
--- a/lib/chef/server_api.rb
+++ b/lib/chef/server_api.rb
@@ -22,6 +22,7 @@ require 'chef/http/cookie_manager'
require 'chef/http/decompressor'
require 'chef/http/json_input'
require 'chef/http/json_output'
+require 'chef/http/remote_request_id'
class Chef
class ServerAPI < Chef::HTTP
@@ -37,5 +38,6 @@ class Chef
use Chef::HTTP::CookieManager
use Chef::HTTP::Decompressor
use Chef::HTTP::Authenticator
+ use Chef::HTTP::RemoteRequestID
end
-end \ No newline at end of file
+end
diff --git a/lib/chef/util/editor.rb b/lib/chef/util/editor.rb
new file mode 100644
index 0000000000..973cf48e30
--- /dev/null
+++ b/lib/chef/util/editor.rb
@@ -0,0 +1,92 @@
+#
+# Author:: Chris Bandy (<bandy.chris@gmail.com>)
+# Copyright:: Copyright (c) 2014 Opscode, 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.
+#
+
+class Chef
+ class Util
+ class Editor
+ attr_reader :lines
+
+ def initialize(lines)
+ @lines = lines.to_a.clone
+ end
+
+ def append_line_after(search, line_to_append)
+ lines = []
+
+ @lines.each do |line|
+ lines << line
+ lines << line_to_append if line.match(search)
+ end
+
+ (lines.length - @lines.length).tap { @lines = lines }
+ end
+
+ def append_line_if_missing(search, line_to_append)
+ count = 0
+
+ unless @lines.find { |line| line.match(search) }
+ count = 1
+ @lines << line_to_append
+ end
+
+ count
+ end
+
+ def remove_lines(search)
+ count = 0
+
+ @lines.delete_if do |line|
+ count += 1 if line.match(search)
+ end
+
+ count
+ end
+
+ def replace(search, replace)
+ count = 0
+
+ @lines.map! do |line|
+ if line.match(search)
+ count += 1
+ line.gsub!(search, replace)
+ else
+ line
+ end
+ end
+
+ count
+ end
+
+ def replace_lines(search, replace)
+ count = 0
+
+ @lines.map! do |line|
+ if line.match(search)
+ count += 1
+ replace
+ else
+ line
+ end
+ end
+
+ count
+ end
+ end
+ end
+end
+
diff --git a/lib/chef/util/file_edit.rb b/lib/chef/util/file_edit.rb
index bb19435a12..92cefb4bb4 100644
--- a/lib/chef/util/file_edit.rb
+++ b/lib/chef/util/file_edit.rb
@@ -15,8 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+require 'chef/util/editor'
require 'fileutils'
-require 'tempfile'
class Chef
class Util
@@ -24,108 +24,76 @@ class Chef
private
- attr_accessor :original_pathname, :contents, :file_edited
+ attr_reader :editor, :original_pathname
public
def initialize(filepath)
+ raise ArgumentError, "File '#{filepath}' does not exist" unless File.exist?(filepath)
+ @editor = Editor.new(File.open(filepath, &:readlines))
@original_pathname = filepath
@file_edited = false
+ end
- raise ArgumentError, "File doesn't exist" unless File.exist? @original_pathname
- @contents = File.open(@original_pathname) { |f| f.readlines }
+ # return if file has been edited
+ def file_edited?
+ @file_edited
end
#search the file line by line and match each line with the given regex
#if matched, replace the whole line with newline.
def search_file_replace_line(regex, newline)
- search_match(regex, newline, 'r', 1)
+ @changes = (editor.replace_lines(regex, newline) > 0) || @changes
end
#search the file line by line and match each line with the given regex
#if matched, replace the match (all occurances) with the replace parameter
def search_file_replace(regex, replace)
- search_match(regex, replace, 'r', 2)
+ @changes = (editor.replace(regex, replace) > 0) || @changes
end
#search the file line by line and match each line with the given regex
#if matched, delete the line
def search_file_delete_line(regex)
- search_match(regex, " ", 'd', 1)
+ @changes = (editor.remove_lines(regex) > 0) || @changes
end
#search the file line by line and match each line with the given regex
#if matched, delete the match (all occurances) from the line
def search_file_delete(regex)
- search_match(regex, " ", 'd', 2)
+ search_file_replace(regex, '')
end
#search the file line by line and match each line with the given regex
#if matched, insert newline after each matching line
def insert_line_after_match(regex, newline)
- search_match(regex, newline, 'i', 1)
+ @changes = (editor.append_line_after(regex, newline) > 0) || @changes
end
#search the file line by line and match each line with the given regex
#if not matched, insert newline at the end of the file
def insert_line_if_no_match(regex, newline)
- search_match(regex, newline, 'i', 2)
+ @changes = (editor.append_line_if_missing(regex, newline) > 0) || @changes
+ end
+
+ def unwritten_changes?
+ !!@changes
end
#Make a copy of old_file and write new file out (only if file changed)
def write_file
-
- # file_edited is false when there was no match in the whole file and thus no contents have changed.
- if file_edited
+ if @changes
backup_pathname = original_pathname + ".old"
FileUtils.cp(original_pathname, backup_pathname, :preserve => true)
File.open(original_pathname, "w") do |newfile|
- contents.each do |line|
+ editor.lines.each do |line|
newfile.puts(line)
end
newfile.flush
end
+ @file_edited = true
end
- self.file_edited = false
- end
-
- private
-
- #helper method to do the match, replace, delete, and insert operations
- #command is the switch of delete, replace, and insert ('d', 'r', 'i')
- #method is to control operation on whole line or only the match (1 for line, 2 for match)
- def search_match(regex, replace, command, method)
-
- #convert regex to a Regexp object (if not already is one) and store it in exp.
- exp = Regexp.new(regex)
-
- #loop through contents and do the appropriate operation depending on 'command' and 'method'
- new_contents = []
-
- contents.each do |line|
- if line.match(exp)
- self.file_edited = true
- case
- when command == 'r'
- new_contents << ((method == 1) ? replace : line.gsub!(exp, replace))
- when command == 'd'
- if method == 2
- new_contents << line.gsub!(exp, "")
- end
- when command == 'i'
- new_contents << line
- new_contents << replace unless method == 2
- end
- else
- new_contents << line
- end
- end
- if command == 'i' && method == 2 && ! file_edited
- new_contents << replace
- self.file_edited = true
- end
-
- self.contents = new_contents
+ @changes = false
end
end
end
diff --git a/lib/chef/version.rb b/lib/chef/version.rb
index 3c3972d0b4..f55160b56c 100644
--- a/lib/chef/version.rb
+++ b/lib/chef/version.rb
@@ -17,7 +17,7 @@
class Chef
CHEF_ROOT = File.dirname(File.expand_path(File.dirname(__FILE__)))
- VERSION = '11.10.4'
+ VERSION = '11.12.0.rc.0'
end
# NOTE: the Chef::Version class is defined in version_class.rb
diff --git a/lib/chef/win32/api/installer.rb b/lib/chef/win32/api/installer.rb
new file mode 100644
index 0000000000..745802d260
--- /dev/null
+++ b/lib/chef/win32/api/installer.rb
@@ -0,0 +1,166 @@
+#
+# Author:: Bryan McLellan <btm@loftninjas.org>
+# Copyright:: Copyright (c) 2014 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/exceptions'
+require 'chef/win32/api'
+require 'chef/win32/error'
+require 'pathname'
+
+class Chef
+ module ReservedNames::Win32
+ module API
+ module Installer
+ extend Chef::ReservedNames::Win32
+ extend Chef::ReservedNames::Win32::API
+
+ ###############################################
+ # Win32 API Constants
+ ###############################################
+
+
+ ###############################################
+ # Win32 API Bindings
+ ###############################################
+
+ ffi_lib 'msi'
+
+=begin
+UINT MsiOpenPackage(
+ _In_ LPCTSTR szPackagePath,
+ _Out_ MSIHANDLE *hProduct
+);
+=end
+ safe_attach_function :msi_open_package, :MsiOpenPackageExA, [ :string, :int, :pointer ], :int
+
+=begin
+UINT MsiGetProductProperty(
+ _In_ MSIHANDLE hProduct,
+ _In_ LPCTSTR szProperty,
+ _Out_ LPTSTR lpValueBuf,
+ _Inout_ DWORD *pcchValueBuf
+);
+=end
+ safe_attach_function :msi_get_product_property, :MsiGetProductPropertyA, [ :pointer, :pointer, :pointer, :pointer ], :int
+
+=begin
+UINT MsiGetProductInfo(
+ _In_ LPCTSTR szProduct,
+ _In_ LPCTSTR szProperty,
+ _Out_ LPTSTR lpValueBuf,
+ _Inout_ DWORD *pcchValueBuf
+);
+=end
+ safe_attach_function :msi_get_product_info, :MsiGetProductInfoA, [ :pointer, :pointer, :pointer, :pointer ], :int
+
+=begin
+UINT MsiCloseHandle(
+ _In_ MSIHANDLE hAny
+);
+=end
+ safe_attach_function :msi_close_handle, :MsiCloseHandle, [ :pointer ], :int
+
+ ###############################################
+ # Helpers
+ ###############################################
+
+ # Opens a Microsoft Installer (MSI) file from an absolute path and returns the specified property
+ def get_product_property(package_path, property_name)
+ pkg_ptr = open_package(package_path)
+
+ buffer = 0.chr
+ buffer_length = FFI::Buffer.new(:long).write_long(0)
+
+ # Fetch the length of the property
+ status = msi_get_product_property(pkg_ptr.read_pointer, property_name, buffer, buffer_length)
+
+ # We expect error ERROR_MORE_DATA (234) here because we passed a buffer length of 0
+ if status != 234
+ msg = "msi_get_product_property: returned unknown error #{status} when retrieving #{property_name}: "
+ msg << Chef::ReservedNames::Win32::Error.format_message(status)
+ raise Chef::Exceptions::Package, msg
+ end
+
+ buffer_length = FFI::Buffer.new(:long).write_long(buffer_length.read_long + 1)
+ buffer = 0.chr * buffer_length.read_long
+
+ # Fetch the property
+ status = msi_get_product_property(pkg_ptr.read_pointer, property_name, buffer, buffer_length)
+
+ if status != 0
+ msg = "msi_get_product_property: returned unknown error #{status} when retrieving #{property_name}: "
+ msg << Chef::ReservedNames::Win32::Error.format_message(status)
+ raise Chef::Exceptions::Package, msg
+ end
+
+ msi_close_handle(pkg_ptr.read_pointer)
+ return buffer
+ end
+
+ # Opens a Microsoft Installer (MSI) file from an absolute path and returns a pointer to a handle
+ # Remember to close the handle with msi_close_handle()
+ def open_package(package_path)
+ # MsiOpenPackage expects a perfect absolute Windows path to the MSI
+ raise ArgumentError, "Provided path '#{package_path}' must be an absolute path" unless Pathname.new(package_path).absolute?
+
+ pkg_ptr = FFI::MemoryPointer.new(:pointer, 4)
+ status = msi_open_package(package_path, 1, pkg_ptr)
+ case status
+ when 0
+ # success
+ else
+ raise Chef::Exceptions::Package, "msi_open_package: unexpected status #{status}: #{Chef::ReservedNames::Win32::Error.format_message(status)}"
+ end
+ return pkg_ptr
+ end
+
+ # All installed product_codes should have a VersionString
+ # Returns a version if installed, nil if not installed
+ def get_installed_version(product_code)
+ version = 0.chr
+ version_length = FFI::Buffer.new(:long).write_long(0)
+
+ status = msi_get_product_info(product_code, "VersionString", version, version_length)
+
+ return nil if status == 1605 # ERROR_UNKNOWN_PRODUCT (0x645)
+
+ # We expect error ERROR_MORE_DATA (234) here because we passed a buffer length of 0
+ if status != 234
+ msg = "msi_get_product_info: product code '#{product_code}' returned unknown error #{status} when retrieving VersionString: "
+ msg << Chef::ReservedNames::Win32::Error.format_message(status)
+ raise Chef::Exceptions::Package, msg
+ end
+
+ # We could fetch the product version now that we know the variable length, but we don't need it here.
+
+ version_length = FFI::Buffer.new(:long).write_long(version_length.read_long + 1)
+ version = 0.chr * version_length.read_long
+
+ status = msi_get_product_info(product_code, "VersionString", version, version_length)
+
+ if status != 0
+ msg = "msi_get_product_info: product code '#{product_code}' returned unknown error #{status} when retrieving VersionString: "
+ msg << Chef::ReservedNames::Win32::Error.format_message(status)
+ raise Chef::Exceptions::Package, msg
+ end
+
+ version
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/win32/version.rb b/lib/chef/win32/version.rb
index e008ff15e8..7f5fcceead 100644
--- a/lib/chef/win32/version.rb
+++ b/lib/chef/win32/version.rb
@@ -116,9 +116,17 @@ class Chef
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms724439(v=vs.85).aspx
require 'ruby-wmi'
+ # CHEF-4888: Work around ruby #2618, expected to be fixed in Ruby 2.1.0
+ # https://github.com/ruby/ruby/commit/588504b20f5cc880ad51827b93e571e32446e5db
+ # https://github.com/ruby/ruby/commit/27ed294c7134c0de582007af3c915a635a6506cd
+
+ WIN32OLE.ole_initialize
+
os_info = WMI::Win32_OperatingSystem.find(:first)
os_version = os_info.send('Version')
+ WIN32OLE.ole_uninitialize
+
# The operating system version is a string in the following form
# that can be split into components based on the '.' delimiter:
# MajorVersionNumber.MinorVersionNumber.BuildNumber
diff --git a/spec/data/standalone_cookbook/Gemfile b/spec/data/standalone_cookbook/Gemfile
new file mode 100644
index 0000000000..9c961848d8
--- /dev/null
+++ b/spec/data/standalone_cookbook/Gemfile
@@ -0,0 +1 @@
+source "https://rubygems.org/" \ No newline at end of file
diff --git a/spec/data/standalone_cookbook/chefignore b/spec/data/standalone_cookbook/chefignore
new file mode 100644
index 0000000000..cd18e699c1
--- /dev/null
+++ b/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/spec/data/standalone_cookbook/recipes/default.rb b/spec/data/standalone_cookbook/recipes/default.rb
new file mode 100644
index 0000000000..c2fa53be32
--- /dev/null
+++ b/spec/data/standalone_cookbook/recipes/default.rb
@@ -0,0 +1,3 @@
+#
+# Nothing ot see here
+# \ No newline at end of file
diff --git a/spec/data/standalone_cookbook/vendor/bundle/ruby/2.0.0/gems/multi_json-1.9.0/lib/multi_json.rb b/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/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/spec/functional/dsl/reboot_pending_spec.rb b/spec/functional/dsl/reboot_pending_spec.rb
new file mode 100644
index 0000000000..10d667f7bd
--- /dev/null
+++ b/spec/functional/dsl/reboot_pending_spec.rb
@@ -0,0 +1,118 @@
+#
+# Author:: Bryan McLellan <btm@loftninjas.org>
+# Copyright:: Copyright (c) 2014 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/dsl/reboot_pending"
+require "chef/win32/registry"
+require "spec_helper"
+
+describe Chef::DSL::RebootPending, :windows_only do
+ def run_ohai
+ ohai = Ohai::System.new
+ # Would be nice to limit this to platform/kernel/arch etc for Ohai 7
+ ohai.all_plugins
+ node.consume_external_attrs(ohai.data,{})
+
+ ohai
+ end
+
+ def registry_safe?
+ !registry.value_exists?('HKLM\SYSTEM\CurrentControlSet\Control\Session Manager', { :name => 'PendingFileRenameOperations' }) ||
+ !registry.key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired') ||
+ !registry.key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired') ||
+ !registry.key_exists?('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile')
+ end
+
+ let(:node) { Chef::Node.new }
+ let(:events) { Chef::EventDispatch::Dispatcher.new }
+ let!(:ohai) { run_ohai } # Ensure we have necessary node data
+ let(:run_context) { Chef::RunContext.new(node, {}, events) }
+ let(:recipe) { Chef::Recipe.new("a windows cookbook", "the windows recipe", run_context) }
+ let(:registry) { Chef::Win32::Registry.new(run_context) }
+
+ describe "reboot_pending?" do
+
+ context "when there is nothing to indicate a reboot is pending" do
+ it { expect(recipe.reboot_pending?).to be_false }
+ end
+
+ describe 'HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations' do
+ it "returns true if the registry value exists" do
+ pending "Found existing registry keys" unless registry_safe?
+ registry.set_value('HKLM\SYSTEM\CurrentControlSet\Control\Session Manager',
+ { :name => 'PendingFileRenameOperations', :type => :multi_string, :data => ['\??\C:\foo.txt|\??\C:\bar.txt'] })
+
+ expect(recipe.reboot_pending?).to be_true
+ end
+
+ after do
+ if registry_safe?
+ registry.delete_value('HKLM\SYSTEM\CurrentControlSet\Control\Session Manager', { :name => 'PendingFileRenameOperations' })
+ end
+ end
+ end
+
+ describe 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired' do
+ it "returns true if the registry key exists" do
+ pending "Found existing registry keys" unless registry_safe?
+ registry.create_key('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired', false)
+
+ expect(recipe.reboot_pending?).to be_true
+ end
+
+ after do
+ if registry_safe?
+ registry.delete_key('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired', false)
+ end
+ end
+ end
+
+ describe 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired' do
+ it "returns true if the registry key exists" do
+ pending "Permissions are limited to 'TrustedInstaller' by default"
+ pending "Found existing registry keys" unless registry_safe?
+ registry.create_key('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired', false)
+
+ expect(recipe.reboot_pending?).to be_true
+ end
+
+ after do
+ if registry_safe?
+ registry.delete_key('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired', false)
+ end
+ end
+ end
+
+ describe 'HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile\Flags' do
+ it "returns true if the registry key exists" do
+ pending "Found existing registry keys" unless registry_safe?
+ registry.create_key('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile', true)
+ registry.set_value('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile',
+ { :name => 'Flags', :type => :dword, :data => 3 })
+
+ expect(recipe.reboot_pending?).to be_true
+ end
+
+ after do
+ if registry_safe?
+ registry.delete_value('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile', { :name => 'Flags' })
+ registry.delete_key('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile', false)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/functional/resource/base.rb b/spec/functional/resource/base.rb
index 13438c1759..056db39877 100644
--- a/spec/functional/resource/base.rb
+++ b/spec/functional/resource/base.rb
@@ -22,9 +22,7 @@ def ohai
# provider is platform-dependent, we need platform ohai data:
@OHAI_SYSTEM ||= begin
ohai = Ohai::System.new
- ohai.require_plugin("os")
- ohai.require_plugin("platform")
- ohai.require_plugin("passwd")
+ ohai.all_plugins("platform")
ohai
end
end
diff --git a/spec/functional/resource/deploy_revision_spec.rb b/spec/functional/resource/deploy_revision_spec.rb
index 14e6e69d6d..9ff1391e35 100644
--- a/spec/functional/resource/deploy_revision_spec.rb
+++ b/spec/functional/resource/deploy_revision_spec.rb
@@ -45,7 +45,7 @@ describe Chef::Resource::DeployRevision, :unix_only => true do
before(:all) do
@ohai = Ohai::System.new
- @ohai.require_plugin("os")
+ @ohai.all_plugins("os")
end
let(:node) do
@@ -78,6 +78,9 @@ describe Chef::Resource::DeployRevision, :unix_only => true do
# This is the third version
let(:previous_rev) { "6d19a6dbecc8e37f5b2277345885c0c783eb8fb1" }
+ # This is the second version
+ let(:second_rev) { "0827e1b0e5043608ac0a824da5c558e252154ad0" }
+
# This is the sixth version, it is on the "with-deploy-scripts" branch
let(:rev_with_in_repo_callbacks) { "2404d015882659754bdb93ad6e4b4d3d02691a82" }
@@ -100,6 +103,7 @@ describe Chef::Resource::DeployRevision, :unix_only => true do
let(:basic_deploy_resource) do
Chef::Resource::DeployRevision.new(deploy_directory, run_context).tap do |r|
+ r.name "deploy-revision-unit-test"
r.repo git_bundle_repo
r.symlink_before_migrate({})
r.symlinks({})
@@ -127,6 +131,34 @@ describe Chef::Resource::DeployRevision, :unix_only => true do
end
end
+ let(:deploy_to_previous_rev_again) do
+ basic_deploy_resource.dup.tap do |r|
+ r.revision(previous_rev)
+ r.restart_command shell_restart_command(:deploy_to_previous_rev_again)
+ end
+ end
+
+ let(:deploy_to_second_rev) do
+ basic_deploy_resource.dup.tap do |r|
+ r.revision(second_rev)
+ r.restart_command shell_restart_command(:deploy_to_second_rev)
+ end
+ end
+
+ let(:deploy_to_second_rev_again) do
+ basic_deploy_resource.dup.tap do |r|
+ r.revision(second_rev)
+ r.restart_command shell_restart_command(:deploy_to_second_rev_again)
+ end
+ end
+
+ let(:deploy_to_second_rev_again_again) do
+ basic_deploy_resource.dup.tap do |r|
+ r.revision(second_rev)
+ r.restart_command shell_restart_command(:deploy_to_second_rev_again_again)
+ end
+ end
+
# Computes the full path for +path+ relative to the deploy directory
def rel_path(path)
File.expand_path(path, deploy_directory)
@@ -306,6 +338,165 @@ describe Chef::Resource::DeployRevision, :unix_only => true do
end
end
+ describe "back to a previously deployed revision where resource rev == latest revision (explicit rollback)" do
+ before do
+ deploy_to_previous_rev.run_action(:deploy)
+ @previous_rev_all_releases = deploy_to_previous_rev.provider_for_action(:deploy).all_releases
+ deploy_to_latest_rev.run_action(:deploy)
+ @latest_rev_all_releases = deploy_to_latest_rev.provider_for_action(:deploy).all_releases
+ deploy_to_latest_rev_again.run_action(:rollback)
+ @previous_rev_again_all_releases = deploy_to_latest_rev_again.provider_for_action(:deploy).all_releases
+ end
+
+ the_app_is_deployed_at_revision(:previous_rev)
+
+ it "restarts the application after rolling back" do
+ actual_operations_order.should == %w[deploy_to_previous_rev deploy_to_latest_rev deploy_to_latest_rev_again]
+ end
+
+ it "is marked updated" do
+ deploy_to_latest_rev_again.should be_updated_by_last_action
+ end
+
+ it "deploys the right code" do
+ IO.read(rel_path("current/app/app.rb")).should include("this is the third version of the app")
+ end
+
+ it "all_releases after first deploy should have one entry" do
+ @previous_rev_all_releases.length.should == 1
+ end
+
+ it "all_releases after second deploy should have two entries" do
+ @latest_rev_all_releases.length.should == 2
+ end
+
+ it "all_releases after rollback should have one entry" do
+ @previous_rev_again_all_releases.length.should == 1
+ end
+
+ it "all_releases after rollback should be the same as after the first deploy" do
+ @previous_rev_again_all_releases.should == @previous_rev_all_releases
+ end
+
+ end
+
+ describe "back to a previously deployed revision where resource rev == previous revision (explicit rollback)" do
+ before do
+ deploy_to_previous_rev.run_action(:deploy)
+ @previous_rev_all_releases = deploy_to_previous_rev.provider_for_action(:deploy).all_releases
+ deploy_to_latest_rev.run_action(:deploy)
+ @latest_rev_all_releases = deploy_to_latest_rev.provider_for_action(:deploy).all_releases
+ deploy_to_previous_rev_again.run_action(:rollback)
+ # FIXME: only difference with previous test is using latest_rev_again insetad of previous_rev_again
+ @previous_rev_again_all_releases = deploy_to_latest_rev_again.provider_for_action(:deploy).all_releases
+ end
+
+ the_app_is_deployed_at_revision(:previous_rev)
+
+ it "restarts the application after rolling back" do
+ actual_operations_order.should == %w[deploy_to_previous_rev deploy_to_latest_rev deploy_to_previous_rev_again]
+ end
+
+ it "is marked updated" do
+ deploy_to_previous_rev_again.should be_updated_by_last_action
+ end
+
+ it "deploys the right code" do
+ IO.read(rel_path("current/app/app.rb")).should include("this is the third version of the app")
+ end
+
+ it "all_releases after first deploy should have one entry" do
+ @previous_rev_all_releases.length.should == 1
+ end
+
+ it "all_releases after second deploy should have two entries" do
+ @latest_rev_all_releases.length.should == 2
+ end
+
+ it "all_releases after rollback should have one entry" do
+ @previous_rev_again_all_releases.length.should == 1
+ end
+
+ it "all_releases after rollback should be the same as after the first deploy" do
+ @previous_rev_again_all_releases.should == @previous_rev_all_releases
+ end
+ end
+
+ describe "back to a previously deployed revision where resource rev == latest revision (explicit rollback)" do
+ before do
+ deploy_to_second_rev.run_action(:deploy)
+ @first_deploy_all_releases = deploy_to_second_rev.provider_for_action(:deploy).all_releases
+ deploy_to_previous_rev.run_action(:deploy)
+ @second_deploy_all_releases = deploy_to_previous_rev.provider_for_action(:deploy).all_releases
+ deploy_to_previous_rev_again.run_action(:rollback)
+ @third_deploy_all_releases = deploy_to_previous_rev_again.provider_for_action(:deploy).all_releases
+ deploy_to_latest_rev.run_action(:deploy)
+ @fourth_deploy_all_releases = deploy_to_latest_rev.provider_for_action(:deploy).all_releases
+ deploy_to_latest_rev_again.run_action(:rollback)
+ @fifth_deploy_all_releases = deploy_to_latest_rev_again.provider_for_action(:deploy).all_releases
+ end
+
+ the_app_is_deployed_at_revision(:second_rev)
+
+ it "restarts the application after rolling back" do
+ actual_operations_order.should == %w[deploy_to_second_rev deploy_to_previous_rev deploy_to_previous_rev_again deploy_to_latest_rev deploy_to_latest_rev_again]
+ end
+
+ it "is marked updated" do
+ deploy_to_latest_rev_again.should be_updated_by_last_action
+ end
+
+ it "deploys the right code" do
+ IO.read(rel_path("current/app/app.rb")).should include("this is the second version of the app")
+ end
+
+ it "all_releases after rollback should have one entry" do
+ @fifth_deploy_all_releases.length.should == 1
+ end
+
+ it "all_releases after rollback should be the same as after the first deploy" do
+ @fifth_deploy_all_releases.should == @first_deploy_all_releases
+ end
+ end
+
+ describe "back to a previously deployed revision where resource rev == latest revision (explicit rollback)" do
+ before do
+ deploy_to_second_rev.run_action(:deploy)
+ @first_deploy_all_releases = deploy_to_second_rev.provider_for_action(:deploy).all_releases
+ deploy_to_previous_rev.run_action(:deploy)
+ @second_deploy_all_releases = deploy_to_previous_rev.provider_for_action(:deploy).all_releases
+ deploy_to_second_rev_again.run_action(:rollback)
+ @third_deploy_all_releases = deploy_to_second_rev_again.provider_for_action(:deploy).all_releases
+ deploy_to_latest_rev.run_action(:deploy)
+ @fourth_deploy_all_releases = deploy_to_latest_rev.provider_for_action(:deploy).all_releases
+ deploy_to_second_rev_again_again.run_action(:rollback)
+ @fifth_deploy_all_releases = deploy_to_second_rev_again_again.provider_for_action(:deploy).all_releases
+ end
+
+ the_app_is_deployed_at_revision(:second_rev)
+
+ it "restarts the application after rolling back" do
+ actual_operations_order.should == %w[deploy_to_second_rev deploy_to_previous_rev deploy_to_second_rev_again deploy_to_latest_rev deploy_to_second_rev_again_again]
+ end
+
+ it "is marked updated" do
+ deploy_to_second_rev_again_again.should be_updated_by_last_action
+ end
+
+ it "deploys the right code" do
+ IO.read(rel_path("current/app/app.rb")).should include("this is the second version of the app")
+ end
+
+ it "all_releases after rollback should have one entry" do
+ @fifth_deploy_all_releases.length.should == 1
+ end
+
+ it "all_releases after rollback should be the same as after the first deploy" do
+ @fifth_deploy_all_releases.should == @first_deploy_all_releases
+ end
+
+ end
+
# CHEF-3435
describe "to a deploy_to path that does not yet exist" do
diff --git a/spec/functional/resource/git_spec.rb b/spec/functional/resource/git_spec.rb
index 7ade6eea21..f0bd94b0c0 100644
--- a/spec/functional/resource/git_spec.rb
+++ b/spec/functional/resource/git_spec.rb
@@ -92,7 +92,7 @@ E
before(:all) do
@ohai = Ohai::System.new
- @ohai.require_plugin("os")
+ @ohai.all_plugins("os")
end
context "working with pathes with special characters" do
diff --git a/spec/functional/resource/ohai_spec.rb b/spec/functional/resource/ohai_spec.rb
new file mode 100644
index 0000000000..b1e4891293
--- /dev/null
+++ b/spec/functional/resource/ohai_spec.rb
@@ -0,0 +1,65 @@
+#
+# Author:: Serdar Sutay (<serdar@opscode.com>)
+# Copyright:: Copyright (c) 2014 Opscode, 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 Chef::Resource::Ohai do
+ let(:ohai) {
+ o = Ohai::System.new
+ o.all_plugins
+ o
+ }
+
+ let(:node) { Chef::Node.new }
+
+ let(:run_context) {
+ node.default[:platform] = ohai[:platform]
+ node.default[:platform_version] = ohai[:platform_version]
+ events = Chef::EventDispatch::Dispatcher.new
+ Chef::RunContext.new(node, {}, events)
+ }
+
+ shared_examples_for "reloaded :uptime" do
+ it "should reload :uptime" do
+ initial_uptime = ohai[:uptime]
+
+ # Sleep for a second so the uptime gets updated.
+ sleep 1
+
+ ohai_resource.run_action(:reload)
+ node[:uptime].should_not == initial_uptime
+ end
+ end
+
+ describe "when reloading all plugins" do
+ let(:ohai_resource) { Chef::Resource::Ohai.new("reload all", run_context)}
+
+ it_behaves_like "reloaded :uptime"
+ end
+
+ describe "when reloading only uptime" do
+ let(:ohai_resource) {
+ r = Chef::Resource::Ohai.new("reload all", run_context)
+ r.plugin("uptime")
+ r
+ }
+
+
+ it_behaves_like "reloaded :uptime"
+ end
+end
diff --git a/spec/functional/resource/powershell_spec.rb b/spec/functional/resource/powershell_spec.rb
index 6bd3b3c1e5..5001e870a9 100644
--- a/spec/functional/resource/powershell_spec.rb
+++ b/spec/functional/resource/powershell_spec.rb
@@ -84,7 +84,7 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
# last line executed -- in this case, we return the status of the
# second to last line. This happens because Powershell gives no
# way for us to determine whether the last operation was a cmdlet
- # or Windows process. Because the latter gives more specified
+ # or Windows process. Because the latter gives more specific
# errors than 0 or 1, we return that instead, which is acceptable
# since callers can test for nonzero rather than testing for 1.
it "returns 1 if the last command was a cmdlet that failed and was preceded by an unsuccessfully executed non-cmdlet Windows binary" do
@@ -111,6 +111,32 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
resource.run_action(:run)
end
+ it "returns 0 for $false as the last line of the script when convert_boolean_return is false" do
+ resource.code "$false"
+ resource.returns(0)
+ resource.run_action(:run)
+ end
+
+ it "returns 0 for $true as the last line of the script when convert_boolean_return is false" do
+ resource.code "$true"
+ resource.returns(0)
+ resource.run_action(:run)
+ end
+
+ it "returns 1 for $false as the last line of the script when convert_boolean_return is true" do
+ resource.convert_boolean_return true
+ resource.code "$false"
+ resource.returns(1)
+ resource.run_action(:run)
+ end
+
+ it "returns 0 for $true as the last line of the script when convert_boolean_return is true" do
+ resource.convert_boolean_return true
+ resource.code "$true"
+ resource.returns(0)
+ resource.run_action(:run)
+ end
+
it "executes a script with a 64-bit process on a 64-bit OS, otherwise a 32-bit process" do
resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}")
resource.returns(0)
@@ -177,6 +203,241 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
end
end
+ describe "when executing guards" do
+
+ before(:each) do
+ resource.not_if.clear
+ resource.only_if.clear
+ resource.guard_interpreter :powershell_script
+ end
+
+ it "evaluates a succeeding not_if block using cmd.exe as false by default" do
+ resource.guard_interpreter :default
+ resource.not_if "exit /b 0"
+ resource.should_skip?(:run).should be_true
+ end
+
+ it "evaluates a failing not_if block using cmd.exe as true by default" do
+ resource.guard_interpreter :default
+ resource.not_if "exit /b 2"
+ resource.should_skip?(:run).should be_false
+ end
+
+ it "evaluates an succeeding only_if block using cmd.exe as true by default" do
+ resource.guard_interpreter :default
+ resource.only_if "exit /b 0"
+ resource.should_skip?(:run).should be_false
+ end
+
+ it "evaluates a failing only_if block using cmd.exe as false by default" do
+ resource.guard_interpreter :default
+ resource.only_if "exit /b 2"
+ resource.should_skip?(:run).should be_true
+ end
+
+ it "evaluates a powershell $false for a not_if block as true" do
+ resource.not_if "$false"
+ resource.should_skip?(:run).should be_false
+ end
+
+ it "evaluates a powershell $true for a not_if block as false" do
+ resource.not_if "$true"
+ resource.should_skip?(:run).should be_true
+ end
+
+ it "evaluates a powershell $false for an only_if block as false" do
+ resource.only_if "$false"
+ resource.should_skip?(:run).should be_true
+ end
+
+ it "evaluates a powershell $true for a only_if block as true" do
+ resource.only_if "$true"
+ resource.should_skip?(:run).should be_false
+ end
+
+ it "evaluates a not_if block using powershell.exe" do
+ resource.not_if "exit([int32](![System.Environment]::CommandLine.Contains('powershell.exe')))"
+ resource.should_skip?(:run).should be_true
+ end
+
+ it "evaluates an only_if block using powershell.exe" do
+ resource.only_if "exit([int32](![System.Environment]::CommandLine.Contains('powershell.exe')))"
+ resource.should_skip?(:run).should be_false
+ end
+
+ it "evaluates a not_if block as false" do
+ resource.not_if { false }
+ resource.should_skip?(:run).should be_false
+ end
+
+ it "evaluates a not_if block as true" do
+ resource.not_if { true }
+ resource.should_skip?(:run).should be_true
+ end
+
+ it "evaluates an only_if block as false" do
+ resource.only_if { false }
+ resource.should_skip?(:run).should be_true
+ end
+
+ it "evaluates an only_if block as true" do
+ resource.only_if { true }
+ resource.should_skip?(:run).should be_false
+ end
+
+ it "evaluates a non-zero powershell exit status for not_if as true" do
+ resource.not_if "exit 37"
+ resource.should_skip?(:run).should be_false
+ end
+
+ it "evaluates a zero powershell exit status for not_if as false" do
+ resource.not_if "exit 0"
+ resource.should_skip?(:run).should be_true
+ end
+
+ it "evaluates a failed executable exit status for not_if as false" do
+ resource.not_if windows_process_exit_code_not_found_content
+ resource.should_skip?(:run).should be_false
+ end
+
+ it "evaluates a successful executable exit status for not_if as true" do
+ resource.not_if windows_process_exit_code_success_content
+ resource.should_skip?(:run).should be_true
+ end
+
+ it "evaluates a failed executable exit status for only_if as false" do
+ resource.only_if windows_process_exit_code_not_found_content
+ resource.should_skip?(:run).should be_true
+ end
+
+ it "evaluates a successful executable exit status for only_if as true" do
+ resource.only_if windows_process_exit_code_success_content
+ resource.should_skip?(:run).should be_false
+ end
+
+ it "evaluates a failed cmdlet exit status for not_if as true" do
+ resource.not_if "throw 'up'"
+ resource.should_skip?(:run).should be_false
+ end
+
+ it "evaluates a successful cmdlet exit status for not_if as true" do
+ resource.not_if "cd ."
+ resource.should_skip?(:run).should be_true
+ end
+
+ it "evaluates a failed cmdlet exit status for only_if as false" do
+ resource.only_if "throw 'up'"
+ resource.should_skip?(:run).should be_true
+ end
+
+ it "evaluates a successful cmdlet exit status for only_if as true" do
+ resource.only_if "cd ."
+ resource.should_skip?(:run).should be_false
+ end
+
+ it "evaluates a not_if block using the cwd guard parameter" do
+ custom_cwd = "#{ENV['SystemRoot']}\\system32\\drivers\\etc"
+ resource.not_if "exit ! [int32]($pwd.path -eq '#{custom_cwd}')", :cwd => custom_cwd
+ resource.should_skip?(:run).should be_true
+ end
+
+ it "evaluates an only_if block using the cwd guard parameter" do
+ custom_cwd = "#{ENV['SystemRoot']}\\system32\\drivers\\etc"
+ resource.only_if "exit ! [int32]($pwd.path -eq '#{custom_cwd}')", :cwd => custom_cwd
+ resource.should_skip?(:run).should be_false
+ end
+
+ it "inherits cwd from the parent resource for only_if" do
+ custom_cwd = "#{ENV['SystemRoot']}\\system32\\drivers\\etc"
+ resource.cwd custom_cwd
+ resource.only_if "exit ! [int32]($pwd.path -eq '#{custom_cwd}')"
+ resource.should_skip?(:run).should be_false
+ end
+
+ it "inherits cwd from the parent resource for not_if" do
+ custom_cwd = "#{ENV['SystemRoot']}\\system32\\drivers\\etc"
+ resource.cwd custom_cwd
+ resource.not_if "exit ! [int32]($pwd.path -eq '#{custom_cwd}')"
+ resource.should_skip?(:run).should be_true
+ end
+
+ it "evaluates a 64-bit resource with a 64-bit guard and interprets boolean false as zero status code", :windows64_only do
+ resource.architecture :x86_64
+ resource.only_if "exit [int32]($env:PROCESSOR_ARCHITECTURE -ne 'AMD64')"
+ resource.should_skip?(:run).should be_false
+ end
+
+ it "evaluates a 64-bit resource with a 64-bit guard and interprets boolean true as nonzero status code", :windows64_only do
+ resource.architecture :x86_64
+ resource.only_if "exit [int32]($env:PROCESSOR_ARCHITECTURE -eq 'AMD64')"
+ resource.should_skip?(:run).should be_true
+ end
+
+ it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code" do
+ resource.architecture :i386
+ resource.only_if "exit [int32]($env:PROCESSOR_ARCHITECTURE -ne 'X86')"
+ resource.should_skip?(:run).should be_false
+ end
+
+ it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code" do
+ resource.architecture :i386
+ resource.only_if "exit [int32]($env:PROCESSOR_ARCHITECTURE -eq 'X86')"
+ resource.should_skip?(:run).should be_true
+ end
+
+ it "evaluates a simple boolean false as nonzero status code when convert_boolean_return is true for only_if" do
+ resource.convert_boolean_return true
+ resource.only_if "$false"
+ resource.should_skip?(:run).should be_true
+ end
+
+ it "evaluates a simple boolean false as nonzero status code when convert_boolean_return is true for not_if" do
+ resource.convert_boolean_return true
+ resource.not_if "$false"
+ resource.should_skip?(:run).should be_false
+ end
+
+ it "evaluates a simple boolean true as 0 status code when convert_boolean_return is true for only_if" do
+ resource.convert_boolean_return true
+ resource.only_if "$true"
+ resource.should_skip?(:run).should be_false
+ end
+
+ it "evaluates a simple boolean true as 0 status code when convert_boolean_return is true for not_if" do
+ resource.convert_boolean_return true
+ resource.not_if "$true"
+ resource.should_skip?(:run).should be_true
+ end
+
+ it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code using convert_boolean_return for only_if" do
+ resource.convert_boolean_return true
+ resource.architecture :i386
+ resource.only_if "$env:PROCESSOR_ARCHITECTURE -eq 'X86'"
+ resource.should_skip?(:run).should be_false
+ end
+
+ it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code using convert_boolean_return for not_if" do
+ resource.convert_boolean_return true
+ resource.architecture :i386
+ resource.not_if "$env:PROCESSOR_ARCHITECTURE -ne 'X86'"
+ resource.should_skip?(:run).should be_false
+ end
+
+ it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code using convert_boolean_return for only_if" do
+ resource.convert_boolean_return true
+ resource.architecture :i386
+ resource.only_if "$env:PROCESSOR_ARCHITECTURE -ne 'X86'"
+ resource.should_skip?(:run).should be_true
+ end
+
+ it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code using convert_boolean_return for not_if" do
+ resource.convert_boolean_return true
+ resource.architecture :i386
+ resource.not_if "$env:PROCESSOR_ARCHITECTURE -eq 'X86'"
+ resource.should_skip?(:run).should be_true
+ end
+ end
+
def get_script_output
script_output = File.read(script_output_path)
end
diff --git a/spec/functional/resource/registry_spec.rb b/spec/functional/resource/registry_spec.rb
index 455c31c283..2d24eee6a3 100644
--- a/spec/functional/resource/registry_spec.rb
+++ b/spec/functional/resource/registry_spec.rb
@@ -118,10 +118,10 @@ describe Chef::Resource::RegistryKey, :windows_only do
@resource_reporter = Chef::ResourceReporter.new(@rest_client)
@events.register(@resource_reporter)
- @run_id = @resource_reporter.run_id
@run_status = Chef::RunStatus.new(@node, @events)
-
@resource_reporter.run_started(@run_status)
+ @run_id = @resource_reporter.run_id
+
@new_resource.cookbook_name = "monkey"
@cookbook_version = double("Cookbook::Version", :version => "1.2.3")
@@ -265,7 +265,7 @@ describe Chef::Resource::RegistryKey, :windows_only do
@new_resource.key(reg_child + '\Slitheen\Raxicoricofallapatorius')
@new_resource.values([{:name=>"BriskWalk",:type=>:string,:data=>"is good for health"}])
@new_resource.recursive(false)
- lambda{@new_resource.run_action(:create)}.should_not raise_error
+ @new_resource.run_action(:create) # should not raise_error
@registry.key_exists?(reg_child + '\Slitheen').should == false
@registry.key_exists?(reg_child + '\Slitheen\Raxicoricofallapatorius').should == false
end
@@ -376,7 +376,7 @@ describe Chef::Resource::RegistryKey, :windows_only do
@new_resource.key(reg_child + '\Zygons\Zygor')
@new_resource.values([{:name=>"BriskWalk",:type=>:string,:data=>"is good for health"}])
@new_resource.recursive(false)
- lambda{@new_resource.run_action(:create_if_missing)}.should_not raise_error
+ @new_resource.run_action(:create_if_missing) # should not raise_error
@registry.key_exists?(reg_child + '\Zygons').should == false
@registry.key_exists?(reg_child + '\Zygons\Zygor').should == false
end
@@ -547,7 +547,6 @@ describe Chef::Resource::RegistryKey, :windows_only do
@new_resource.values([{:name=>"BriskWalk",:type=>:string,:data=>"is good for health"}])
@new_resource.recursive(false)
@new_resource.run_action(:delete_key)
- @new_resource.should_not raise_error
end
it "does nothing if the action is delete_key" do
@new_resource.key(reg_parent + '\OpscodeWhyRun')
diff --git a/spec/functional/win32/versions_spec.rb b/spec/functional/win32/versions_spec.rb
index 0b8a65114c..b983b711da 100644
--- a/spec/functional/win32/versions_spec.rb
+++ b/spec/functional/win32/versions_spec.rb
@@ -22,7 +22,7 @@ if Chef::Platform.windows?
require 'ruby-wmi'
end
-describe "Chef::ReservedNames::Win32::Version", :windows_only do
+describe "Chef::ReservedNames::Win32::Version", :windows_only, :not_supported_on_win2k3 do
before do
host = WMI::Win32_OperatingSystem.find(:first)
@@ -57,7 +57,7 @@ describe "Chef::ReservedNames::Win32::Version", :windows_only do
end
end
end
-
+
context "Win32 version object" do
it "should have have one method for each marketing version" do
versions = 0
@@ -88,7 +88,7 @@ describe "Chef::ReservedNames::Win32::Version", :windows_only do
for_each_windows_version { |method_name| @version.send(method_name.to_sym) }
end
end
-
+
context "Windows Operating System version" do
it "should match the version from WMI" do
@current_os_version.should include(@version.marketing_name)
diff --git a/spec/integration/client/client_spec.rb b/spec/integration/client/client_spec.rb
index bca9ed4854..6357f1e2c0 100644
--- a/spec/integration/client/client_spec.rb
+++ b/spec/integration/client/client_spec.rb
@@ -215,5 +215,19 @@ EOM
result = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default' -z", :cwd => chef_dir)
result.error!
end
+
+ it "should complete with success when setting the run list with -r" do
+ file 'config/client.rb', <<EOM
+chef_server_url 'http://omg.com/blah'
+cookbook_path "#{path_to('cookbooks')}"
+EOM
+
+ result = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" -r 'x::default' -z", :cwd => chef_dir)
+ result.stdout.should_not include("Overridden Run List")
+ result.stdout.should include("Run List is [recipe[x::default]]")
+ #puts result.stdout
+ result.error!
+ end
+
end
end
diff --git a/spec/integration/knife/chefignore_spec.rb b/spec/integration/knife/chefignore_spec.rb
index f2a8d9ad75..4c3d2fa3aa 100644
--- a/spec/integration/knife/chefignore_spec.rb
+++ b/spec/integration/knife/chefignore_spec.rb
@@ -38,14 +38,13 @@ describe 'chefignore tests' do
file 'data_bags/bag1/chefignore', chefignore
file 'cookbooks/cookbook1/chefignore', chefignore
- it 'nothing is ignored' do
+ 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
-/cookbooks/cookbook1/x.json
/data_bags/
/data_bags/bag1/
/data_bags/bag1/x.json
diff --git a/spec/integration/knife/raw_spec.rb b/spec/integration/knife/raw_spec.rb
index 69630061ac..fafd0a47ee 100644
--- a/spec/integration/knife/raw_spec.rb
+++ b/spec/integration/knife/raw_spec.rb
@@ -22,6 +22,7 @@ require 'chef/knife/show'
describe 'knife raw' do
extend IntegrationSupport
include KnifeSupport
+ include AppServerSupport
when_the_chef_server "has one of each thing" do
client 'x', '{}'
@@ -55,7 +56,7 @@ EOM
end
it 'knife raw /blarghle returns 404' do
- knife('raw /blarghle').should_fail(/ERROR: Server responded with error 404 "Not Found"/)
+ knife('raw /blarghle').should_fail(/ERROR: Server responded with error 404 "Not Found\s*"/)
end
it 'knife raw -m DELETE /roles/x succeeds', :pending => (RUBY_VERSION < "1.9") do
@@ -165,19 +166,16 @@ EOM
context 'When a server returns raw json' do
before :each do
- @real_chef_server_url = Chef::Config.chef_server_url
Chef::Config.chef_server_url = "http://127.0.0.1:9018"
app = lambda do |env|
[200, {'Content-Type' => 'application/json' }, ['{ "x": "y", "a": "b" }'] ]
end
- @raw_server = Puma::Server.new(app, Puma::Events.new(STDERR, STDOUT))
- @raw_server.add_tcp_listener("127.0.0.1", 9018)
- @raw_server.run
+ @raw_server, @raw_server_thread = start_app_server(app, 9018)
end
after :each do
- Chef::Config.chef_server_url = @real_chef_server_url
- @raw_server.stop(true)
+ @raw_server.shutdown if @raw_server
+ @raw_server_thread.kill if @raw_server_thread
end
it 'knife raw /blah returns the prettified json', :pending => (RUBY_VERSION < "1.9") do
@@ -198,19 +196,16 @@ EOM
context 'When a server returns text' do
before :each do
- @real_chef_server_url = Chef::Config.chef_server_url
Chef::Config.chef_server_url = "http://127.0.0.1:9018"
app = lambda do |env|
[200, {'Content-Type' => 'text' }, ['{ "x": "y", "a": "b" }'] ]
end
- @raw_server = Puma::Server.new(app, Puma::Events.new(STDERR, STDOUT))
- @raw_server.add_tcp_listener("127.0.0.1", 9018)
- @raw_server.run
+ @raw_server, @raw_server_thread = start_app_server(app, 9018)
end
after :each do
- Chef::Config.chef_server_url = @real_chef_server_url
- @raw_server.stop(true)
+ @raw_server.shutdown if @raw_server
+ @raw_server_thread.kill if @raw_server_thread
end
it 'knife raw /blah returns the raw text' do
diff --git a/spec/integration/knife/redirection_spec.rb b/spec/integration/knife/redirection_spec.rb
index 5af9fd36e1..2ed49a7b24 100644
--- a/spec/integration/knife/redirection_spec.rb
+++ b/spec/integration/knife/redirection_spec.rb
@@ -15,38 +15,30 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require 'puma'
require 'support/shared/integration/integration_helper'
require 'chef/knife/list'
describe 'redirection' do
extend IntegrationSupport
include KnifeSupport
+ include AppServerSupport
when_the_chef_server 'has a role' do
role 'x', {}
context 'and another server redirects to it with 302' do
before :each do
- @real_chef_server_url = Chef::Config.chef_server_url
+ real_chef_server_url = Chef::Config.chef_server_url
Chef::Config.chef_server_url = "http://127.0.0.1:9018"
app = lambda do |env|
- [302, {'Content-Type' => 'text','Location' => "#{@real_chef_server_url}#{env['PATH_INFO']}" }, ['302 found'] ]
- end
- @redirector_server = Puma::Server.new(app, Puma::Events.new(STDERR, STDOUT))
- @redirector_server.add_tcp_listener("127.0.0.1", 9018)
- @redirector_server.run
- Timeout::timeout(5) do
- until @redirector_server.running
- sleep(0.01)
- end
- raise @server_error if @server_error
+ [302, {'Content-Type' => 'text','Location' => "#{real_chef_server_url}#{env['PATH_INFO']}" }, ['302 found'] ]
end
+ @redirector_server, @redirector_server_thread = start_app_server(app, 9018)
end
after :each do
- Chef::Config.chef_server_url = @real_chef_server_url
- @redirector_server.stop(true)
+ @redirector_server.shutdown if @redirector_server
+ @redirector_thread.kill if @redirector_thread
end
it 'knife list /roles returns the role' do
diff --git a/spec/integration/solo/solo_spec.rb b/spec/integration/solo/solo_spec.rb
index cd4678f94d..23ec8d0bad 100644
--- a/spec/integration/solo/solo_spec.rb
+++ b/spec/integration/solo/solo_spec.rb
@@ -42,6 +42,25 @@ E
end
+ when_the_repository "has a cookbook with an undeclared dependency" do
+ file 'cookbooks/x/metadata.rb', 'version "1.0.0"'
+ file 'cookbooks/x/recipes/default.rb', 'include_recipe "ancient::aliens"'
+
+ file 'cookbooks/ancient/metadata.rb', 'version "1.0.0"'
+ file 'cookbooks/ancient/recipes/aliens.rb', 'print "it was aliens"'
+
+ it "should exit with an error" do
+ file 'config/solo.rb', <<EOM
+cookbook_path "#{path_to('cookbooks')}"
+file_cache_path "#{path_to('config/cache')}"
+EOM
+ result = shell_out("ruby bin/chef-solo -c \"#{path_to('config/solo.rb')}\" -o 'x::default' -l debug", :cwd => chef_dir)
+ result.exitstatus.should == 0 # For CHEF-5120 this becomes 1
+ result.stdout.should include("WARN: MissingCookbookDependency")
+ end
+ end
+
+
when_the_repository "has a cookbook with a recipe with sleep" do
directory 'logs'
file 'logs/runs.log', ''
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 5098a1fb85..88e38bdc4b 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -87,8 +87,7 @@ Dir["spec/support/**/*.rb"].
each { |f| require f }
OHAI_SYSTEM = Ohai::System.new
-OHAI_SYSTEM.require_plugin("os")
-OHAI_SYSTEM.require_plugin("platform")
+OHAI_SYSTEM.all_plugins("platform")
TEST_PLATFORM = OHAI_SYSTEM["platform"].dup.freeze
TEST_PLATFORM_VERSION = OHAI_SYSTEM["platform_version"].dup.freeze
diff --git a/spec/support/shared/functional/windows_script.rb b/spec/support/shared/functional/windows_script.rb
index afeb4c029c..fc06fb55d0 100644
--- a/spec/support/shared/functional/windows_script.rb
+++ b/spec/support/shared/functional/windows_script.rb
@@ -23,8 +23,7 @@ shared_context Chef::Resource::WindowsScript do
before(:all) do
ohai_reader = Ohai::System.new
- ohai_reader.require_plugin("os")
- ohai_reader.require_plugin("windows::platform")
+ ohai_reader.all_plugins("platform")
new_node = Chef::Node.new
new_node.consume_external_attrs(ohai_reader.data,{})
@@ -39,7 +38,7 @@ shared_context Chef::Resource::WindowsScript do
end
before(:each) do
-k File.delete(script_output_path) if File.exists?(script_output_path)
+ File.delete(script_output_path) if File.exists?(script_output_path)
end
after(:each) do
diff --git a/spec/support/shared/integration/app_server_support.rb b/spec/support/shared/integration/app_server_support.rb
new file mode 100644
index 0000000000..a0d5e7fa5c
--- /dev/null
+++ b/spec/support/shared/integration/app_server_support.rb
@@ -0,0 +1,42 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Author:: Ho-Sheng Hsiao (<hosh@opscode.com>)
+# Copyright:: Copyright (c) 2012, 2013 Opscode, 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 'rack'
+require 'stringio'
+
+module AppServerSupport
+ def start_app_server(app, port)
+ server = nil
+ thread = Thread.new do
+ Rack::Handler::WEBrick.run(app,
+ :Port => 9018,
+ :AccessLog => [],
+ :Logger => WEBrick::Log::new(StringIO.new, 7)
+ ) do |found_server|
+ server = found_server
+ end
+ end
+ Timeout::timeout(5) do
+ until server && server.status == :Running
+ sleep(0.01)
+ end
+ end
+ [server, thread]
+ end
+end
diff --git a/spec/support/shared/integration/integration_helper.rb b/spec/support/shared/integration/integration_helper.rb
index 0c4bf990af..abed4c2715 100644
--- a/spec/support/shared/integration/integration_helper.rb
+++ b/spec/support/shared/integration/integration_helper.rb
@@ -23,6 +23,7 @@ require 'chef/config'
require 'chef_zero/rspec'
require 'json'
require 'support/shared/integration/knife_support'
+require 'support/shared/integration/app_server_support'
require 'spec_helper'
module IntegrationSupport
diff --git a/spec/support/shared/unit/script_resource.rb b/spec/support/shared/unit/script_resource.rb
index 5f37506df6..1137958420 100644
--- a/spec/support/shared/unit/script_resource.rb
+++ b/spec/support/shared/unit/script_resource.rb
@@ -48,5 +48,43 @@ shared_examples_for "a script resource" do
@resource.flags.should eql("-f")
end
+ describe "when executing guards" do
+ let(:resource) { @resource }
+
+ before(:each) do
+ node = Chef::Node.new
+
+ node.automatic[:platform] = "debian"
+ node.automatic[:platform_version] = "6.0"
+
+ events = Chef::EventDispatch::Dispatcher.new
+ run_context = Chef::RunContext.new(node, {}, events)
+ resource.run_context = run_context
+ resource.code 'echo hi'
+ end
+
+ it "inherits exactly the :cwd, :environment, :group, :path, :user, and :umask attributes from a parent resource class" do
+ inherited_difference = Chef::Resource::Script.guard_inherited_attributes -
+ [:cwd, :environment, :group, :path, :user, :umask ]
+
+ inherited_difference.should == []
+ end
+
+ it "when guard_interpreter is set to the default value, the guard command string should be evaluated by command execution and not through a resource" do
+ Chef::Resource::Conditional.any_instance.should_not_receive(:evaluate_block)
+ Chef::Resource::Conditional.any_instance.should_receive(:evaluate_command).and_return(true)
+ Chef::GuardInterpreter::ResourceGuardInterpreter.any_instance.should_not_receive(:evaluate_action)
+ resource.only_if 'echo hi'
+ resource.should_skip?(:run).should == nil
+ end
+
+ it "when a valid guard_interpreter resource is specified, a block should be used to evaluate the guard" do
+ Chef::GuardInterpreter::DefaultGuardInterpreter.any_instance.should_not_receive(:evaluate)
+ Chef::GuardInterpreter::ResourceGuardInterpreter.any_instance.should_receive(:evaluate_action).and_return(true)
+ resource.guard_interpreter :script
+ resource.only_if 'echo hi'
+ resource.should_skip?(:run).should == nil
+ end
+ end
end
diff --git a/spec/unit/api_client/registration_spec.rb b/spec/unit/api_client/registration_spec.rb
index 845c217f72..15a9c30482 100644
--- a/spec/unit/api_client/registration_spec.rb
+++ b/spec/unit/api_client/registration_spec.rb
@@ -22,16 +22,45 @@ require 'tempfile'
require 'chef/api_client/registration'
describe Chef::ApiClient::Registration do
+
let(:key_location) do
make_tmpname("client-registration-key")
end
- let(:registration) { Chef::ApiClient::Registration.new("silent-bob", key_location) }
+ let(:client_name) { "silent-bob" }
+
+ subject(:registration) { Chef::ApiClient::Registration.new(client_name, key_location) }
- let :private_key_data do
+ let(:private_key_data) do
File.open(Chef::Config[:validation_key], "r") {|f| f.read.chomp }
end
+ let(:http_mock) { double("Chef::REST mock") }
+
+ let(:expected_post_data) do
+ { :name => client_name, :admin => false }
+ end
+
+ let(:expected_put_data) do
+ { :name => client_name, :admin => false, :private_key => true }
+ end
+
+ let(:server_v10_response) do
+ {"uri" => "https://chef.local/clients/#{client_name}",
+ "private_key" => "--begin rsa key etc--"}
+ end
+
+ # Server v11 includes `json_class` on all replies
+ let(:server_v11_response) do
+ response = Chef::ApiClient.new
+ response.name(client_name)
+ response.private_key("--begin rsa key etc--")
+ response
+ end
+
+ let(:response_409) { Net::HTTPConflict.new("1.1", "409", "Conflict") }
+ let(:exception_409) { Net::HTTPServerException.new("409 conflict", response_409) }
+
before do
Chef::Config[:validation_client_name] = "test-validator"
Chef::Config[:validation_key] = File.expand_path('ssl/private_key.pem', CHEF_SPEC_DATA)
@@ -39,8 +68,6 @@ describe Chef::ApiClient::Registration do
after do
File.unlink(key_location) if File.exist?(key_location)
- Chef::Config[:validation_client_name] = nil
- Chef::Config[:validation_key] = nil
end
it "has an HTTP client configured with validator credentials" do
@@ -50,57 +77,107 @@ describe Chef::ApiClient::Registration do
end
describe "when creating/updating the client on the server" do
- let(:http_mock) { double("Chef::REST mock") }
-
before do
registration.stub(:http_api).and_return(http_mock)
end
it "creates a new ApiClient on the server using the validator identity" do
- response = {"uri" => "https://chef.local/clients/silent-bob",
- "private_key" => "--begin rsa key etc--"}
http_mock.should_receive(:post).
- with("clients", :name => 'silent-bob', :admin => false).
- and_return(response)
- registration.create_or_update.should == response
+ with("clients", expected_post_data).
+ and_return(server_v10_response)
+ registration.create_or_update.should == server_v10_response
registration.private_key.should == "--begin rsa key etc--"
end
context "and the client already exists on a Chef 10 server" do
it "requests a new key from the server and saves it" do
- response = {"name" => "silent-bob", "private_key" => "--begin rsa key etc--" }
-
- response_409 = Net::HTTPConflict.new("1.1", "409", "Conflict")
- exception_409 = Net::HTTPServerException.new("409 conflict", response_409)
-
- http_mock.should_receive(:post).and_raise(exception_409)
+ http_mock.should_receive(:post).with("clients", expected_post_data).
+ and_raise(exception_409)
http_mock.should_receive(:put).
- with("clients/silent-bob", :name => 'silent-bob', :admin => false, :private_key => true).
- and_return(response)
- registration.create_or_update.should == response
+ with("clients/#{client_name}", expected_put_data).
+ and_return(server_v10_response)
+ registration.create_or_update.should == server_v10_response
registration.private_key.should == "--begin rsa key etc--"
end
end
context "and the client already exists on a Chef 11 server" do
it "requests a new key from the server and saves it" do
- response = Chef::ApiClient.new
- response.name("silent-bob")
- response.private_key("--begin rsa key etc--")
-
- response_409 = Net::HTTPConflict.new("1.1", "409", "Conflict")
- exception_409 = Net::HTTPServerException.new("409 conflict", response_409)
-
http_mock.should_receive(:post).and_raise(exception_409)
http_mock.should_receive(:put).
- with("clients/silent-bob", :name => 'silent-bob', :admin => false, :private_key => true).
- and_return(response)
- registration.create_or_update.should == response
+ with("clients/#{client_name}", expected_put_data).
+ and_return(server_v11_response)
+ registration.create_or_update.should == server_v11_response
registration.private_key.should == "--begin rsa key etc--"
end
end
end
+ context "when local key generation is enabled", :nofocus do
+ let(:generated_private_key_pem) { IO.read(File.expand_path('ssl/private_key.pem', CHEF_SPEC_DATA)) }
+ let(:generated_private_key) { OpenSSL::PKey::RSA.new(generated_private_key_pem) }
+ let(:generated_public_key) { generated_private_key.public_key }
+
+ let(:expected_post_data) do
+ { :name => client_name, :admin => false, :public_key => generated_public_key.to_pem }
+ end
+
+ let(:expected_put_data) do
+ { :name => client_name, :admin => false, :public_key => generated_public_key.to_pem }
+ end
+
+ let(:create_with_pkey_response) do
+ {
+ "uri" => "",
+ "public_key" => generated_public_key.to_pem
+ }
+ end
+
+ let(:update_with_pkey_response) do
+ {"name"=>client_name,
+ "admin"=>false,
+ "public_key"=> generated_public_key,
+ "validator"=>false,
+ "private_key"=>false,
+ "clientname"=>client_name}
+ end
+
+
+ before do
+ registration.stub(:http_api).and_return(http_mock)
+ Chef::Config.local_key_generation = true
+ OpenSSL::PKey::RSA.should_receive(:generate).with(2048).and_return(generated_private_key)
+ end
+
+ it "posts a locally generated public key to the server to create a client" do
+ http_mock.should_receive(:post).
+ with("clients", expected_post_data).
+ and_return(create_with_pkey_response)
+ registration.create_or_update.should == create_with_pkey_response
+ registration.private_key.should == generated_private_key_pem
+ end
+
+ it "puts a locally generated public key to the server to update a client" do
+ http_mock.should_receive(:post).
+ with("clients", expected_post_data).
+ and_raise(exception_409)
+ http_mock.should_receive(:put).
+ with("clients/#{client_name}", expected_put_data).
+ and_return(update_with_pkey_response)
+ registration.create_or_update.should == update_with_pkey_response
+ registration.private_key.should == generated_private_key_pem
+ end
+
+ it "writes the generated private key to disk" do
+ http_mock.should_receive(:post).
+ with("clients", expected_post_data).
+ and_return(create_with_pkey_response)
+ registration.run
+ IO.read(key_location).should == generated_private_key_pem
+ end
+
+ end
+
describe "when writing the private key to disk" do
before do
registration.stub(:private_key).and_return('--begin rsa key etc--')
@@ -125,16 +202,12 @@ describe Chef::ApiClient::Registration do
describe "when registering a client" do
- let(:http_mock) { double("Chef::REST mock") }
-
before do
registration.stub(:http_api).and_return(http_mock)
end
it "creates the client on the server and writes the key" do
- response = {"uri" => "http://chef.local/clients/silent-bob",
- "private_key" => "--begin rsa key etc--" }
- http_mock.should_receive(:post).ordered.and_return(response)
+ http_mock.should_receive(:post).ordered.and_return(server_v10_response)
registration.run
IO.read(key_location).should == "--begin rsa key etc--"
end
@@ -149,9 +222,7 @@ describe Chef::ApiClient::Registration do
http_mock.should_receive(:post).ordered.and_raise(exception_500) # 4
http_mock.should_receive(:post).ordered.and_raise(exception_500) # 5
- response = {"uri" => "http://chef.local/clients/silent-bob",
- "private_key" => "--begin rsa key etc--" }
- http_mock.should_receive(:post).ordered.and_return(response)
+ http_mock.should_receive(:post).ordered.and_return(server_v10_response)
registration.run
IO.read(key_location).should == "--begin rsa key etc--"
end
diff --git a/spec/unit/api_client_spec.rb b/spec/unit/api_client_spec.rb
index 4ccd64bafe..8657fa59a8 100644
--- a/spec/unit/api_client_spec.rb
+++ b/spec/unit/api_client_spec.rb
@@ -164,6 +164,52 @@ describe Chef::ApiClient do
end
+ describe "when loading from JSON" do
+ before do
+ end
+
+ before(:each) do
+ client = {
+ "name" => "black",
+ "clientname" => "black",
+ "public_key" => "crowes",
+ "private_key" => "monkeypants",
+ "admin" => true,
+ "validator" => true,
+ "json_class" => "Chef::ApiClient"
+ }
+ @http_client = double("Chef::REST mock")
+ Chef::REST.stub(:new).and_return(@http_client)
+ @http_client.should_receive(:get).with("clients/black").and_return(client)
+ @client = Chef::ApiClient.load(client['name'])
+ end
+
+ it "should deserialize to a Chef::ApiClient object" do
+ @client.should be_a_kind_of(Chef::ApiClient)
+ end
+
+ it "preserves the name" do
+ @client.name.should == "black"
+ end
+
+ it "preserves the public key" do
+ @client.public_key.should == "crowes"
+ end
+
+ it "preserves the admin status" do
+ @client.admin.should be_a_kind_of(Chef::TrueClass)
+ end
+
+ it "preserves the 'validator' status" do
+ @client.validator.should be_a_kind_of(Chef::TrueClass)
+ end
+
+ it "includes the private key if present" do
+ @client.private_key.should == "monkeypants"
+ end
+
+ end
+
describe "with correctly configured API credentials" do
before do
Chef::Config[:node_name] = "silent-bob"
diff --git a/spec/unit/application/client_spec.rb b/spec/unit/application/client_spec.rb
index f84932073f..8b4ea6a077 100644
--- a/spec/unit/application/client_spec.rb
+++ b/spec/unit/application/client_spec.rb
@@ -127,7 +127,7 @@ describe Chef::Application::Client, "configure_chef" do
end
describe Chef::Application::Client, "run_application", :unix_only do
- before do
+ before(:each) do
@pipe = IO.pipe
@app = Chef::Application::Client.new
@app.stub(:run_chef_client) do
@@ -147,4 +147,51 @@ describe Chef::Application::Client, "run_application", :unix_only do
IO.select([@pipe[0]], nil, nil, 0).should_not be_nil
@pipe[0].gets.should == "finished\n"
end
+
+ describe "when splay is set" do
+ before do
+ Chef::Config[:splay] = 10
+ Chef::Config[:interval] = 10
+
+ run_count = 0
+
+ # uncomment to debug failures...
+ # Chef::Log.init($stderr)
+ # Chef::Log.level = :debug
+
+ @app.stub(:run_chef_client) do
+
+ run_count += 1
+ if run_count > 3
+ exit 0
+ end
+
+ # If everything is fine, sending USR1 to self should prevent
+ # app to go into splay sleep forever.
+ Process.kill("USR1", Process.pid)
+ end
+
+ number_of_sleep_calls = 0
+
+ # This is a very complicated way of writing
+ # @app.should_receive(:sleep).once.
+ # We have to do it this way because the main loop of
+ # Chef::Application::Client swallows most exceptions, and we need to be
+ # able to expose our expectation failures to the parent process in the test.
+ @app.stub(:sleep) do |arg|
+ number_of_sleep_calls += 1
+ if number_of_sleep_calls > 1
+ exit 127
+ end
+ end
+ end
+
+ it "shouldn't sleep when sent USR1" do
+ pid = fork do
+ @app.run_application
+ end
+ _pid, result = Process.waitpid2(pid)
+ result.exitstatus.should == 0
+ end
+ end
end
diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb
index 58a643d403..9688cce2f4 100644
--- a/spec/unit/client_spec.rb
+++ b/spec/unit/client_spec.rb
@@ -24,29 +24,54 @@ require 'chef/run_context'
require 'chef/rest'
require 'rbconfig'
-shared_examples_for Chef::Client do
+describe Chef::Client do
+
+ let(:hostname) { "hostname" }
+ let(:machinename) { "machinename.example.org" }
+ let(:fqdn) { "hostname.example.org" }
+
+ let(:ohai_data) do
+ { :fqdn => fqdn,
+ :hostname => hostname,
+ :machinename => machinename,
+ :platform => 'example-platform',
+ :platform_version => 'example-platform-1.0',
+ :data => {}
+ }
+ end
+
+ let(:ohai_system) do
+ ohai_system = double( "Ohai::System",
+ :all_plugins => true,
+ :data => ohai_data)
+ ohai_system.stub(:[]) do |key|
+ ohai_data[key]
+ end
+ ohai_system
+ end
+
+ let(:node) do
+ Chef::Node.new.tap do |n|
+ n.name(fqdn)
+ n.chef_environment("_default")
+ end
+ end
+
+ let(:json_attribs) { nil }
+ let(:client_opts) { {} }
+
+ let(:client) do
+ Chef::Client.new(json_attribs, client_opts).tap do |c|
+ c.node = node
+ end
+ end
+
before do
Chef::Log.logger = Logger.new(StringIO.new)
# Node/Ohai data
- @hostname = "hostname"
- @fqdn = "hostname.example.org"
- Chef::Config[:node_name] = @fqdn
- ohai_data = { :fqdn => @fqdn,
- :hostname => @hostname,
- :platform => 'example-platform',
- :platform_version => 'example-platform-1.0',
- :data => {} }
- ohai_data.stub(:all_plugins).and_return(true)
- ohai_data.stub(:data).and_return(ohai_data)
- Ohai::System.stub(:new).and_return(ohai_data)
-
- @node = Chef::Node.new
- @node.name(@fqdn)
- @node.chef_environment("_default")
-
- @client = Chef::Client.new
- @client.node = @node
+ #Chef::Config[:node_name] = fqdn
+ Ohai::System.stub(:new).and_return(ohai_system)
end
describe "authentication protocol selection" do
@@ -58,7 +83,7 @@ shared_examples_for Chef::Client do
it "does not force the authentication protocol to 1.1" do
Chef::Config[:node_name] = ("f" * 90)
# ugly that this happens as a side effect of a getter :(
- @client.node_name
+ client.node_name
Chef::Config[:authentication_protocol_version].should == "1.0"
end
end
@@ -67,7 +92,7 @@ shared_examples_for Chef::Client do
it "sets the authentication protocol to version 1.1" do
Chef::Config[:node_name] = ("f" * 91)
# ugly that this happens as a side effect of a getter :(
- @client.node_name
+ client.node_name
Chef::Config[:authentication_protocol_version].should == "1.1"
end
end
@@ -75,9 +100,6 @@ shared_examples_for Chef::Client do
describe "configuring output formatters" do
context "when no formatter has been configured" do
- before do
- @client = Chef::Client.new
- end
context "and STDOUT is a TTY" do
before do
@@ -85,7 +107,7 @@ shared_examples_for Chef::Client do
end
it "configures the :doc formatter" do
- @client.formatters_for_run.should == [[:doc]]
+ client.formatters_for_run.should == [[:doc]]
end
context "and force_logger is set" do
@@ -95,7 +117,7 @@ shared_examples_for Chef::Client do
it "configures the :null formatter" do
Chef::Config[:force_logger].should be_true
- @client.formatters_for_run.should == [[:null]]
+ client.formatters_for_run.should == [[:null]]
end
end
@@ -108,7 +130,7 @@ shared_examples_for Chef::Client do
end
it "configures the :null formatter" do
- @client.formatters_for_run.should == [[:null]]
+ client.formatters_for_run.should == [[:null]]
end
context "and force_formatter is set" do
@@ -116,7 +138,7 @@ shared_examples_for Chef::Client do
Chef::Config[:force_formatter] = true
end
it "it configures the :doc formatter" do
- @client.formatters_for_run.should == [[:doc]]
+ client.formatters_for_run.should == [[:doc]]
end
end
end
@@ -126,16 +148,15 @@ shared_examples_for Chef::Client do
context "when a formatter is configured" do
context "with no output path" do
before do
- @client = Chef::Client.new
Chef::Config.add_formatter(:min)
end
it "does not configure a default formatter" do
- @client.formatters_for_run.should == [[:min, nil]]
+ client.formatters_for_run.should == [[:min, nil]]
end
it "configures the formatter for STDOUT/STDERR" do
- configured_formatters = @client.configure_formatters
+ configured_formatters = client.configure_formatters
min_formatter = configured_formatters[0]
min_formatter.output.out.should == STDOUT
min_formatter.output.err.should == STDERR
@@ -144,7 +165,6 @@ shared_examples_for Chef::Client do
context "with an output path" do
before do
- @client = Chef::Client.new
@tmpout = Tempfile.open("rspec-for-client-formatter-selection-#{Process.pid}")
Chef::Config.add_formatter(:min, @tmpout.path)
end
@@ -155,7 +175,7 @@ shared_examples_for Chef::Client do
end
it "configures the formatter for the file path" do
- configured_formatters = @client.configure_formatters
+ configured_formatters = client.configure_formatters
min_formatter = configured_formatters[0]
min_formatter.output.out.path.should == @tmpout.path
min_formatter.output.err.path.should == @tmpout.path
@@ -165,93 +185,216 @@ shared_examples_for Chef::Client do
end
end
- describe "run" do
-
- it "should identify the node and run ohai, then register the client" do
- mock_chef_rest_for_node = double("Chef::REST (node)")
- mock_chef_rest_for_cookbook_sync = double("Chef::REST (cookbook sync)")
- mock_chef_rest_for_node_save = double("Chef::REST (node save)")
- mock_chef_runner = double("Chef::Runner")
-
- # --Client.register
- # Make sure Client#register thinks the client key doesn't
- # exist, so it tries to register and create one.
- File.should_receive(:exists?).with(Chef::Config[:client_key]).exactly(1).times.and_return(false)
-
- # Client.register will register with the validation client name.
- Chef::ApiClient::Registration.any_instance.should_receive(:run)
- # Client.register will then turn around create another
- # Chef::REST object, this time with the client key it got from the
- # previous step.
- Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url], @fqdn, Chef::Config[:client_key]).exactly(1).and_return(mock_chef_rest_for_node)
-
- # --Client#build_node
- # looks up the node, which we will return, then later saves it.
- Chef::Node.should_receive(:find_or_create).with(@fqdn).and_return(@node)
-
- # --ResourceReporter#node_load_completed
- # gets a run id from the server for storing resource history
- # (has its own tests, so stubbing it here.)
- Chef::ResourceReporter.any_instance.should_receive(:node_load_completed)
-
- # --ResourceReporter#run_completed
- # updates the server with the resource history
- # (has its own tests, so stubbing it here.)
- Chef::ResourceReporter.any_instance.should_receive(:run_completed)
- # --Client#setup_run_context
- # ---Client#sync_cookbooks -- downloads the list of cookbooks to sync
- #
- Chef::CookbookSynchronizer.any_instance.should_receive(:sync_cookbooks)
- Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url]).and_return(mock_chef_rest_for_cookbook_sync)
- mock_chef_rest_for_cookbook_sync.should_receive(:post).with("environments/_default/cookbook_versions", {:run_list => []}).and_return({})
-
- # --Client#converge
- Chef::Runner.should_receive(:new).and_return(mock_chef_runner)
- mock_chef_runner.should_receive(:converge).and_return(true)
-
- # --Client#save_updated_node
- Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url]).and_return(mock_chef_rest_for_node_save)
- mock_chef_rest_for_node_save.should_receive(:put_rest).with("nodes/#{@fqdn}", @node).and_return(true)
-
- Chef::RunLock.any_instance.should_receive(:acquire)
- Chef::RunLock.any_instance.should_receive(:save_pid)
- Chef::RunLock.any_instance.should_receive(:release)
-
- # Post conditions: check that node has been filled in correctly
- @client.should_receive(:run_started)
- @client.should_receive(:run_completed_successfully)
+ describe "a full client run" do
+ shared_examples_for "a successful client run" do
+ let(:http_node_load) { double("Chef::REST (node)") }
+ let(:http_cookbook_sync) { double("Chef::REST (cookbook sync)") }
+ let(:http_node_save) { double("Chef::REST (node save)") }
+ let(:runner) { double("Chef::Runner") }
- if(Chef::Config[:client_fork] && !windows?)
- require 'stringio'
- if(Chef::Config[:pipe_node])
- pipe_sim = StringIO.new
- pipe_sim.should_receive(:close).exactly(4).and_return(nil)
- res = ''
- pipe_sim.should_receive(:puts) do |string|
- res.replace(string)
- end
- pipe_sim.should_receive(:gets).and_return(res)
- IO.should_receive(:pipe).and_return([pipe_sim, pipe_sim])
- IO.should_receive(:select).and_return(true)
+ let(:api_client_exists?) { false }
+
+ let(:stdout) { StringIO.new }
+ let(:stderr) { StringIO.new }
+
+ let(:enable_fork) { false }
+
+ def stub_for_register
+ # --Client.register
+ # Make sure Client#register thinks the client key doesn't
+ # exist, so it tries to register and create one.
+ File.should_receive(:exists?).with(Chef::Config[:client_key]).exactly(1).times.and_return(api_client_exists?)
+
+ unless api_client_exists?
+ # Client.register will register with the validation client name.
+ Chef::ApiClient::Registration.any_instance.should_receive(:run)
end
- proc_ret = Class.new.new
- proc_ret.should_receive(:success?).and_return(true)
- Process.should_receive(:waitpid2).and_return([1, proc_ret])
- @client.should_receive(:exit).and_return(nil)
- @client.should_receive(:fork) do |&block|
- block.call
+ end
+
+ def stub_for_node_load
+ # Client.register will then turn around create another
+ # Chef::REST object, this time with the client key it got from the
+ # previous step.
+ Chef::REST.should_receive(:new).
+ with(Chef::Config[:chef_server_url], fqdn, Chef::Config[:client_key]).
+ exactly(1).
+ and_return(http_node_load)
+
+ # --Client#build_node
+ # looks up the node, which we will return, then later saves it.
+ Chef::Node.should_receive(:find_or_create).with(fqdn).and_return(node)
+
+ # --ResourceReporter#node_load_completed
+ # gets a run id from the server for storing resource history
+ # (has its own tests, so stubbing it here.)
+ Chef::ResourceReporter.any_instance.should_receive(:node_load_completed)
+ end
+
+ def stub_for_sync_cookbooks
+ # --Client#setup_run_context
+ # ---Client#sync_cookbooks -- downloads the list of cookbooks to sync
+ #
+ Chef::CookbookSynchronizer.any_instance.should_receive(:sync_cookbooks)
+ Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_cookbook_sync)
+ http_cookbook_sync.should_receive(:post).
+ with("environments/_default/cookbook_versions", {:run_list => []}).
+ and_return({})
+ end
+
+ def stub_for_converge
+ # --Client#converge
+ Chef::Runner.should_receive(:new).and_return(runner)
+ runner.should_receive(:converge).and_return(true)
+
+ # --ResourceReporter#run_completed
+ # updates the server with the resource history
+ # (has its own tests, so stubbing it here.)
+ Chef::ResourceReporter.any_instance.should_receive(:run_completed)
+ end
+
+ def stub_for_node_save
+ # --Client#save_updated_node
+ Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_node_save)
+ http_node_save.should_receive(:put_rest).with("nodes/#{fqdn}", node).and_return(true)
+ end
+
+ def stub_for_run
+ Chef::RunLock.any_instance.should_receive(:acquire)
+ Chef::RunLock.any_instance.should_receive(:save_pid)
+ Chef::RunLock.any_instance.should_receive(:release)
+
+ # Post conditions: check that node has been filled in correctly
+ client.should_receive(:run_started)
+ client.should_receive(:run_completed_successfully)
+ end
+
+ before do
+ Chef::Config[:client_fork] = enable_fork
+
+ stub_const("Chef::Client::STDOUT_FD", stdout)
+ stub_const("Chef::Client::STDERR_FD", stderr)
+
+ stub_for_register
+ stub_for_node_load
+ stub_for_sync_cookbooks
+ stub_for_converge
+ stub_for_node_save
+ stub_for_run
+ end
+
+ it "runs ohai, sets up authentication, loads node state, synchronizes policy, and converges" do
+ # This is what we're testing.
+ client.run
+
+ # fork is stubbed, so we can see the outcome of the run
+ node.automatic_attrs[:platform].should == "example-platform"
+ node.automatic_attrs[:platform_version].should == "example-platform-1.0"
+ end
+ end
+
+
+ describe "when running chef-client without fork" do
+
+ include_examples "a successful client run"
+ end
+
+ describe "when running chef-client with forking enabled", :unix_only do
+ include_examples "a successful client run" do
+ let(:process_status) do
+ double("Process::Status")
+ end
+
+ let(:enable_fork) { true }
+
+ before do
+ Process.should_receive(:waitpid2).and_return([1, process_status])
+
+ process_status.should_receive(:success?).and_return(true)
+ client.should_receive(:exit).and_return(nil)
+ client.should_receive(:fork).and_yield
end
end
- # This is what we're testing.
- @client.run
+ end
+
+ describe "when the client key already exists" do
+
+ let(:api_client_exists?) { true }
+
+ include_examples "a successful client run"
+ end
+
+ describe "when an override run list is given" do
+ let(:client_opts) { {:override_runlist => "recipe[override_recipe]"} }
- if(!Chef::Config[:client_fork] || Chef::Config[:pipe_node])
- @node.automatic_attrs[:platform].should == "example-platform"
- @node.automatic_attrs[:platform_version].should == "example-platform-1.0"
+ it "should permit spaces in overriding run list" do
+ Chef::Client.new(nil, :override_runlist => 'role[a], role[b]')
+ end
+
+ describe "when running the client" do
+ include_examples "a successful client run" do
+
+ before do
+ # Client will try to compile and run override_recipe
+ Chef::RunContext::CookbookCompiler.any_instance.should_receive(:compile)
+ end
+
+ def stub_for_sync_cookbooks
+ # --Client#setup_run_context
+ # ---Client#sync_cookbooks -- downloads the list of cookbooks to sync
+ #
+ Chef::CookbookSynchronizer.any_instance.should_receive(:sync_cookbooks)
+ Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_cookbook_sync)
+ http_cookbook_sync.should_receive(:post).
+ with("environments/_default/cookbook_versions", {:run_list => ["override_recipe"]}).
+ and_return({})
+ end
+
+ def stub_for_node_save
+ # Expect NO node save
+ node.should_not_receive(:save)
+ end
+ end
end
end
+ describe "when a permanent run list is passed as an option" do
+
+ include_examples "a successful client run" do
+
+ let(:new_runlist) { "recipe[new_run_list_recipe]" }
+ let(:client_opts) { {:runlist => new_runlist} }
+
+ def stub_for_sync_cookbooks
+ # --Client#setup_run_context
+ # ---Client#sync_cookbooks -- downloads the list of cookbooks to sync
+ #
+ Chef::CookbookSynchronizer.any_instance.should_receive(:sync_cookbooks)
+ Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_cookbook_sync)
+ http_cookbook_sync.should_receive(:post).
+ with("environments/_default/cookbook_versions", {:run_list => ["new_run_list_recipe"]}).
+ and_return({})
+ end
+
+ before do
+ # Client will try to compile and run the new_run_list_recipe, but we
+ # do not create a fixture for this.
+ Chef::RunContext::CookbookCompiler.any_instance.should_receive(:compile)
+ end
+
+ it "sets the new run list on the node" do
+ client.run
+ node.run_list.should == Chef::RunList.new(new_runlist)
+ end
+
+ end
+ end
+
+ end
+
+
+ describe "when handling run failures" do
+
it "should remove the run_lock on failure of #load_node" do
@run_lock = double("Chef::RunLock", :acquire => true)
Chef::RunLock.stub(:new).and_return(@run_lock)
@@ -260,64 +403,64 @@ shared_examples_for Chef::Client do
Chef::EventDispatch::Dispatcher.stub(:new).and_return(@events)
# @events is created on Chef::Client.new, so we need to recreate it after mocking
- @client = Chef::Client.new
- @client.stub(:load_node).and_raise(Exception)
+ client = Chef::Client.new
+ client.stub(:load_node).and_raise(Exception)
@run_lock.should_receive(:release)
if(Chef::Config[:client_fork] && !windows?)
- @client.should_receive(:fork) do |&block|
+ client.should_receive(:fork) do |&block|
block.call
end
end
- lambda { @client.run }.should raise_error(Exception)
+ lambda { client.run }.should raise_error(Exception)
end
+ end
- describe "when notifying other objects of the status of the chef run" do
- before do
- Chef::Client.clear_notifications
- Chef::Node.stub(:find_or_create).and_return(@node)
- @node.stub(:save)
- @client.load_node
- @client.build_node
- end
-
- it "notifies observers that the run has started" do
- notified = false
- Chef::Client.when_run_starts do |run_status|
- run_status.node.should == @node
- notified = true
- end
+ describe "when notifying other objects of the status of the chef run" do
+ before do
+ Chef::Client.clear_notifications
+ Chef::Node.stub(:find_or_create).and_return(node)
+ node.stub(:save)
+ client.load_node
+ client.build_node
+ end
- @client.run_started
- notified.should be_true
+ it "notifies observers that the run has started" do
+ notified = false
+ Chef::Client.when_run_starts do |run_status|
+ run_status.node.should == node
+ notified = true
end
- it "notifies observers that the run has completed successfully" do
- notified = false
- Chef::Client.when_run_completes_successfully do |run_status|
- run_status.node.should == @node
- notified = true
- end
+ client.run_started
+ notified.should be_true
+ end
- @client.run_completed_successfully
- notified.should be_true
+ it "notifies observers that the run has completed successfully" do
+ notified = false
+ Chef::Client.when_run_completes_successfully do |run_status|
+ run_status.node.should == node
+ notified = true
end
- it "notifies observers that the run failed" do
- notified = false
- Chef::Client.when_run_fails do |run_status|
- run_status.node.should == @node
- notified = true
- end
+ client.run_completed_successfully
+ notified.should be_true
+ end
- @client.run_failed
- notified.should be_true
+ it "notifies observers that the run failed" do
+ notified = false
+ Chef::Client.when_run_fails do |run_status|
+ run_status.node.should == node
+ notified = true
end
+
+ client.run_failed
+ notified.should be_true
end
end
describe "build_node" do
it "should expand the roles and recipes for the node" do
- @node.run_list << "role[role_containing_cookbook1]"
+ node.run_list << "role[role_containing_cookbook1]"
role_containing_cookbook1 = Chef::Role.new
role_containing_cookbook1.name("role_containing_cookbook1")
role_containing_cookbook1.run_list << "cookbook1"
@@ -329,37 +472,33 @@ shared_examples_for Chef::Client do
Chef::REST.should_receive(:new).and_return(mock_chef_rest)
# check pre-conditions.
- @node[:roles].should be_nil
- @node[:recipes].should be_nil
+ node[:roles].should be_nil
+ node[:recipes].should be_nil
- @client.policy_builder.stub(:node).and_return(@node)
+ client.policy_builder.stub(:node).and_return(node)
# chefspec and possibly others use the return value of this method
- @client.build_node.should == @node
+ client.build_node.should == node
# check post-conditions.
- @node[:roles].should_not be_nil
- @node[:roles].length.should == 1
- @node[:roles].should include("role_containing_cookbook1")
- @node[:recipes].should_not be_nil
- @node[:recipes].length.should == 1
- @node[:recipes].should include("cookbook1")
+ node[:roles].should_not be_nil
+ node[:roles].length.should == 1
+ node[:roles].should include("role_containing_cookbook1")
+ node[:recipes].should_not be_nil
+ node[:recipes].length.should == 1
+ node[:recipes].should include("cookbook1")
end
end
describe "windows_admin_check" do
- before do
- @client = Chef::Client.new
- end
-
context "platform is not windows" do
before do
Chef::Platform.stub(:windows?).and_return(false)
end
it "shouldn't be called" do
- @client.should_not_receive(:has_admin_privileges?)
- @client.do_windows_admin_check
+ client.should_not_receive(:has_admin_privileges?)
+ client.do_windows_admin_check
end
end
@@ -369,91 +508,46 @@ shared_examples_for Chef::Client do
end
it "should be called" do
- @client.should_receive(:has_admin_privileges?)
- @client.do_windows_admin_check
+ client.should_receive(:has_admin_privileges?)
+ client.do_windows_admin_check
end
context "admin privileges exist" do
before do
- @client.should_receive(:has_admin_privileges?).and_return(true)
+ client.should_receive(:has_admin_privileges?).and_return(true)
end
it "should not log a warning message" do
Chef::Log.should_not_receive(:warn)
- @client.do_windows_admin_check
+ client.do_windows_admin_check
end
context "fatal admin check is configured" do
it "should not raise an exception" do
- @client.do_windows_admin_check.should_not raise_error
+ client.do_windows_admin_check #should not raise
end
end
end
context "admin privileges doesn't exist" do
before do
- @client.should_receive(:has_admin_privileges?).and_return(false)
+ client.should_receive(:has_admin_privileges?).and_return(false)
end
it "should log a warning message" do
Chef::Log.should_receive(:warn)
- @client.do_windows_admin_check
+ client.do_windows_admin_check
end
context "fatal admin check is configured" do
it "should raise an exception" do
- @client.do_windows_admin_check.should_not raise_error
+ client.do_windows_admin_check # should not raise
end
end
end
end
end
- describe "when a run list override is provided" do
- before do
- @node = Chef::Node.new
- @node.name(@fqdn)
- @node.chef_environment("_default")
- @node.automatic_attrs[:platform] = "example-platform"
- @node.automatic_attrs[:platform_version] = "example-platform-1.0"
- end
-
- it "should permit spaces in overriding run list" do
- @client = Chef::Client.new(nil, :override_runlist => 'role[a], role[b]')
- end
-
- it "should override the run list and skip the final node save" do
- @client = Chef::Client.new(nil, :override_runlist => 'role[test_role]')
- @client.node = @node
-
- @node.run_list << "role[role_containing_cookbook1]"
-
- override_role = Chef::Role.new
- override_role.name 'test_role'
- override_role.run_list << 'cookbook1'
-
- original_runlist = @node.run_list.dup
-
- mock_chef_rest = double("Chef::REST")
- mock_chef_rest.should_receive(:get_rest).with("roles/test_role").and_return(override_role)
- Chef::REST.should_receive(:new).and_return(mock_chef_rest)
-
- @node.should_not_receive(:save)
-
- @client.policy_builder.stub(:node).and_return(@node)
- @client.policy_builder.build_node
-
- @node[:roles].should_not be_nil
- @node[:roles].should eql(['test_role'])
- @node[:recipes].should eql(['cookbook1'])
-
- @client.save_updated_node
-
- @node.run_list.should == original_runlist
-
- end
- end
-
describe "assert_cookbook_path_not_empty" do
before do
Chef::Config[:solo] = true
@@ -462,24 +556,46 @@ shared_examples_for Chef::Client do
context "when any directory of cookbook_path contains no cookbook" do
it "raises CookbookNotFound error" do
expect do
- @client.send(:assert_cookbook_path_not_empty, nil)
+ client.send(:assert_cookbook_path_not_empty, nil)
end.to raise_error(Chef::Exceptions::CookbookNotFound, 'None of the cookbook paths set in Chef::Config[:cookbook_path], ["/path/to/invalid/cookbook_path"], contain any cookbooks')
end
end
end
-end
+ describe "setting node name" do
+ context "when machinename, hostname and fqdn are all set" do
+ it "favors the fqdn" do
+ expect(client.node_name).to eql(fqdn)
+ end
+ end
-describe Chef::Client do
- Chef::Config[:client_fork] = false
- it_behaves_like Chef::Client
-end
+ context "when fqdn is missing" do
+ # ohai 7 should always have machinename == return of hostname
+ let(:fqdn) { nil }
+ it "favors the machinename" do
+ expect(client.node_name).to eql(machinename)
+ end
+ end
-describe "Chef::Client Forked" do
- before do
- Chef::Config[:client_fork] = true
- end
+ context "when fqdn and machinename are missing" do
+ # ohai 6 will not have machinename, return the short hostname
+ let(:fqdn) { nil }
+ let(:machinename) { nil }
+ it "falls back to hostname" do
+ expect(client.node_name).to eql(hostname)
+ end
+ end
+
+ context "when they're all missing" do
+ let(:machinename) { nil }
+ let(:hostname) { nil }
+ let(:fqdn) { nil }
- it_behaves_like Chef::Client
+ it "throws an exception" do
+ expect { client.node_name }.to raise_error(Chef::Exceptions::CannotDetermineNodeName)
+ end
+ end
+ end
end
+
diff --git a/spec/unit/cookbook/chefignore_spec.rb b/spec/unit/cookbook/chefignore_spec.rb
index aacb60c012..e529a6d05a 100644
--- a/spec/unit/cookbook/chefignore_spec.rb
+++ b/spec/unit/cookbook/chefignore_spec.rb
@@ -36,4 +36,14 @@ describe Chef::Cookbook::Chefignore do
@chefignore.ignored?('recipes/ignoreme.rb').should be_true
@chefignore.ignored?('recipes/dontignoreme.rb').should be_false
end
+
+ context "when using the single cookbook pattern" do
+ before do
+ @chefignore = Chef::Cookbook::Chefignore.new(File.join(CHEF_SPEC_DATA, 'standalone_cookbook'))
+ end
+
+ it "loads the globs in the chefignore file" do
+ @chefignore.ignores.should =~ %w[recipes/ignoreme.rb ignored vendor/bundle/*]
+ end
+ end
end
diff --git a/spec/unit/cookbook/metadata_spec.rb b/spec/unit/cookbook/metadata_spec.rb
index cba2aff5da..88c4a1a5f5 100644
--- a/spec/unit/cookbook/metadata_spec.rb
+++ b/spec/unit/cookbook/metadata_spec.rb
@@ -402,7 +402,7 @@ describe Chef::Cookbook::Metadata do
@meta.attributes["db/mysql/databases"][:recipes].should == []
end
- it "should allow the default value to be a string, array, or hash" do
+ it "should allow the default value to be a string, array, hash, boolean or numeric" do
lambda {
@meta.attribute("db/mysql/databases", :default => [])
}.should_not raise_error
@@ -413,10 +413,54 @@ describe Chef::Cookbook::Metadata do
@meta.attribute("db/mysql/databases", :default => "alice in chains")
}.should_not raise_error
lambda {
+ @meta.attribute("db/mysql/databases", :default => 1337)
+ }.should_not raise_error
+ lambda {
+ @meta.attribute("db/mysql/databases", :default => true)
+ }.should_not raise_error
+ lambda {
@meta.attribute("db/mysql/databases", :required => :not_gonna_do_it)
}.should raise_error(ArgumentError)
end
+ it "should limit the types allowed in the choice array" do
+ options = {
+ :type => "string",
+ :choice => [ "test1", "test2" ],
+ :default => "test1"
+ }
+ lambda {
+ @meta.attribute("test_cookbook/test", options)
+ }.should_not raise_error
+
+ options = {
+ :type => "boolean",
+ :choice => [ true, false ],
+ :default => true
+ }
+ lambda {
+ @meta.attribute("test_cookbook/test", options)
+ }.should_not raise_error
+
+ options = {
+ :type => "numeric",
+ :choice => [ 1337, 420 ],
+ :default => 1337
+ }
+ lambda {
+ @meta.attribute("test_cookbook/test", options)
+ }.should_not raise_error
+
+ options = {
+ :type => "numeric",
+ :choice => [ true, "false" ],
+ :default => false
+ }
+ lambda {
+ @meta.attribute("test_cookbook/test", options)
+ }.should raise_error
+ end
+
it "should error if default used with calculated" do
lambda {
attrs = {
diff --git a/spec/unit/cookbook/syntax_check_spec.rb b/spec/unit/cookbook/syntax_check_spec.rb
index 85d6950a45..a674f6ab40 100644
--- a/spec/unit/cookbook/syntax_check_spec.rb
+++ b/spec/unit/cookbook/syntax_check_spec.rb
@@ -24,11 +24,21 @@ describe Chef::Cookbook::SyntaxCheck do
let(:cookbook_path) { File.join(CHEF_SPEC_DATA, 'cookbooks', 'openldap') }
let(:syntax_check) { Chef::Cookbook::SyntaxCheck.new(cookbook_path) }
+ let(:open_ldap_cookbook_files) {
+ %w{ attributes/default.rb
+ attributes/smokey.rb
+ definitions/client.rb
+ definitions/server.rb
+ metadata.rb
+ recipes/default.rb
+ recipes/gigantor.rb
+ recipes/one.rb }.map{ |f| File.join(cookbook_path, f) }
+}
+
before do
Chef::Log.logger = Logger.new(StringIO.new)
Chef::Log.level = :warn # suppress "Syntax OK" messages
-
@attr_files = %w{default.rb smokey.rb}.map { |f| File.join(cookbook_path, 'attributes', f) }
@defn_files = %w{client.rb server.rb}.map { |f| File.join(cookbook_path, 'definitions', f)}
@recipes = %w{default.rb gigantor.rb one.rb}.map { |f| File.join(cookbook_path, 'recipes', f) }
@@ -48,6 +58,23 @@ describe Chef::Cookbook::SyntaxCheck do
Chef::Config[:cookbook_path] = File.dirname(cookbook_path)
syntax_check = Chef::Cookbook::SyntaxCheck.for_cookbook(:openldap)
syntax_check.cookbook_path.should == cookbook_path
+ syntax_check.ruby_files.sort.should == open_ldap_cookbook_files.sort
+ end
+
+ it "creates a syntax checker given the cookbook name and cookbook_path" do
+ syntax_check = Chef::Cookbook::SyntaxCheck.for_cookbook(:openldap, File.join(CHEF_SPEC_DATA, 'cookbooks'))
+ syntax_check.cookbook_path.should == cookbook_path
+ syntax_check.ruby_files.sort.should == open_ldap_cookbook_files.sort
+ end
+
+ context "when using a standalone cookbook" do
+ let(:cookbook_path) { File.join(CHEF_SPEC_DATA, 'standalone_cookbook') }
+
+ it "creates a syntax checker given the cookbook name and cookbook_path for a standalone cookbook" do
+ syntax_check = Chef::Cookbook::SyntaxCheck.for_cookbook(:standalone_cookbook, CHEF_SPEC_DATA)
+ syntax_check.cookbook_path.should == cookbook_path
+ syntax_check.ruby_files.should == [File.join(cookbook_path, 'recipes/default.rb')]
+ end
end
describe "when first created" do
diff --git a/spec/unit/cookbook_spec.rb b/spec/unit/cookbook_spec.rb
index ca4f4adc08..9bcea97d98 100644
--- a/spec/unit/cookbook_spec.rb
+++ b/spec/unit/cookbook_spec.rb
@@ -68,16 +68,6 @@ describe Chef::CookbookVersion do
@cookbook.preferred_filename(@node, :files, 'a-filename', 'the-checksum').should be_nil
end
- it "should allow you to include a fully-qualified recipe using the DSL" do
- # DSL method include_recipe allows multiple arguments, so extract the first
- @node.should_receive(:loaded_recipe).with(:openldap, "gigantor")
- recipe = @run_context.include_recipe("openldap::gigantor").first
-
- recipe.recipe_name.should == "gigantor"
- recipe.cookbook_name.should == :openldap
- @run_context.resource_collection[0].name.should == "blanket"
- end
-
it "should raise an ArgumentException if you try to load a bad recipe name" do
lambda { @cookbook.load_recipe("doesnt_exist", @node) }.should raise_error(ArgumentError)
end
diff --git a/spec/unit/dsl/reboot_pending_spec.rb b/spec/unit/dsl/reboot_pending_spec.rb
new file mode 100644
index 0000000000..8576ae168a
--- /dev/null
+++ b/spec/unit/dsl/reboot_pending_spec.rb
@@ -0,0 +1,100 @@
+#
+# Author:: Bryan McLellan <btm@loftninjas.org>
+# Copyright:: Copyright (c) 2014 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/dsl/reboot_pending"
+require "spec_helper"
+
+describe Chef::DSL::RebootPending do
+ describe "reboot_pending?" do
+ describe "in isoloation" do
+ let(:recipe) { Object.new.extend(Chef::DSL::RebootPending) }
+
+ before do
+ recipe.stub(:platform?).and_return(false)
+ end
+
+ context "platform is windows" do
+ before do
+ recipe.stub(:platform?).with('windows').and_return(true)
+ recipe.stub(:registry_key_exists?).and_return(false)
+ recipe.stub(:registry_value_exists?).and_return(false)
+ end
+
+ it 'should return true if "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations" exists' do
+ recipe.stub(:registry_value_exists?).with('HKLM\SYSTEM\CurrentControlSet\Control\Session Manager', { :name => 'PendingFileRenameOperations' }).and_return(true)
+ expect(recipe.reboot_pending?).to be_true
+ end
+
+ it 'should return true if "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" exists' do
+ recipe.stub(:registry_key_exists?).with('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired').and_return(true)
+ expect(recipe.reboot_pending?).to be_true
+ end
+
+ it 'should return true if key "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired" exists' do
+ recipe.stub(:registry_key_exists?).with('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired').and_return(true)
+ expect(recipe.reboot_pending?).to be_true
+ end
+
+ it 'should return true if value "HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile" contains specific data' do
+ recipe.stub(:registry_key_exists?).with('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').and_return(true)
+ recipe.stub(:registry_get_values).with('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').and_return(
+ [{:name => "Flags", :type => :dword, :data => 3}])
+ expect(recipe.reboot_pending?).to be_true
+ end
+ end
+
+ context "platform is ubuntu" do
+ before do
+ recipe.stub(:platform?).with('ubuntu').and_return(true)
+ end
+
+ it 'should return true if /var/run/reboot-required exists' do
+ File.stub(:exists?).with('/var/run/reboot-required').and_return(true)
+ expect(recipe.reboot_pending?).to be_true
+ end
+
+ it 'should return false if /var/run/reboot-required does not exist' do
+ File.stub(:exists?).with('/var/run/reboot-required').and_return(false)
+ expect(recipe.reboot_pending?).to be_false
+ end
+ end
+
+ context "platform is not supported" do
+ it 'should raise an exception' do
+ recipe.stub_chain(:node, :[]).with(:platform).and_return('msdos')
+ expect { recipe.reboot_pending? }.to raise_error(Chef::Exceptions::UnsupportedPlatform)
+ end
+ end
+ end # describe in isolation
+
+ describe "in a recipe" do
+ it "responds to reboot_pending?" do
+ # Chef::Recipe.new(cookbook_name, recipe_name, run_context(node, cookbook_collection, events))
+ recipe = Chef::Recipe.new(nil,nil,Chef::RunContext.new(Chef::Node.new, {}, nil))
+ expect(recipe).to respond_to(:reboot_pending?)
+ end
+ end # describe in a recipe
+
+ describe "in a resource" do
+ it "responds to reboot_pending?" do
+ resource = Chef::Resource::new("Crackerjack::Timing", nil)
+ expect(resource).to respond_to(:reboot_pending?)
+ end
+ end # describe in a resource
+ end
+end
diff --git a/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb b/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb
new file mode 100644
index 0000000000..a016cbfeb8
--- /dev/null
+++ b/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb
@@ -0,0 +1,56 @@
+#
+# Author:: Adam Edwards (<adamed@getchef.com>)
+# Copyright:: Copyright (c) 2014 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 Chef::GuardInterpreter::ResourceGuardInterpreter do
+ before(:each) do
+ node = Chef::Node.new
+
+ node.default["kernel"] = Hash.new
+ node.default["kernel"][:machine] = :x86_64.to_s
+
+ run_context = Chef::RunContext.new(node, nil, nil)
+
+ @resource = Chef::Resource.new("powershell_unit_test", run_context)
+ @resource.stub(:run_action)
+ @resource.stub(:updated).and_return(true)
+ end
+
+ describe "when evaluating a guard resource" do
+ let(:resource) { @resource }
+
+ it "should allow guard interpreter to be set to Chef::Resource::Script" do
+ resource.guard_interpreter(:script)
+ allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(false)
+ resource.only_if("echo hi")
+ end
+
+ it "should allow guard interpreter to be set to Chef::Resource::PowershellScript derived indirectly from Chef::Resource::Script" do
+ resource.guard_interpreter(:powershell_script)
+ allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(false)
+ resource.only_if("echo hi")
+ end
+
+ it "should raise an exception if guard_interpreter is set to a resource not derived from Chef::Resource::Script" do
+ resource.guard_interpreter(:file)
+ expect { resource.only_if("echo hi") }.to raise_error ArgumentError
+ end
+ end
+end
+
diff --git a/spec/unit/http/simple_spec.rb b/spec/unit/http/simple_spec.rb
new file mode 100644
index 0000000000..b33ef1d553
--- /dev/null
+++ b/spec/unit/http/simple_spec.rb
@@ -0,0 +1,32 @@
+#
+# Author:: Serdar Sutay (<serdar@opscode.com>)
+# Copyright:: Copyright (c) 2014 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 Chef::HTTP::Simple do
+ it "should have content length validation middleware after compressor middleware" do
+ client = Chef::HTTP::Simple.new("dummy.com")
+ middlewares = client.instance_variable_get(:@middlewares)
+ content_length = middlewares.find_index { |e| e.is_a? Chef::HTTP::ValidateContentLength }
+ decompressor = middlewares.find_index { |e| e.is_a? Chef::HTTP::Decompressor }
+
+ content_length.should_not be_nil
+ decompressor.should_not be_nil
+ (decompressor < content_length).should be_true
+ end
+end
diff --git a/spec/unit/http/validate_content_length_spec.rb b/spec/unit/http/validate_content_length_spec.rb
new file mode 100644
index 0000000000..091f2b0757
--- /dev/null
+++ b/spec/unit/http/validate_content_length_spec.rb
@@ -0,0 +1,187 @@
+#
+# Author:: Serdar Sutay (<serdar@opscode.com>)
+# Copyright:: Copyright (c) 2014 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'
+require 'stringio'
+
+describe Chef::HTTP::ValidateContentLength do
+ class TestClient < Chef::HTTP
+ use Chef::HTTP::ValidateContentLength
+ end
+
+ let(:method) { "GET" }
+ let(:url) { "http://dummy.com" }
+ let(:headers) { {} }
+ let(:data) { false }
+
+ let(:request) { }
+ let(:return_value) { "200" }
+
+ # Test Variables
+ let(:request_type) { :streaming }
+ let(:content_length_value) { 23 }
+ let(:streaming_length) { 23 }
+ let(:response_body) { "Thanks for checking in." }
+ let(:response_headers) {
+ {
+ "content-length" => content_length_value
+ }
+ }
+
+ let(:response) {
+ m = double('HttpResponse', :body => response_body)
+ m.stub(:[]) do |key|
+ response_headers[key]
+ end
+
+ m
+ }
+
+ let(:middleware) {
+ client = TestClient.new(url)
+ client.middlewares[0]
+ }
+
+ def run_content_length_validation
+ stream_handler = middleware.stream_response_handler(response)
+ middleware.handle_request(method, url, headers, data)
+
+ case request_type
+ when :streaming
+ # First stream the data
+ data_length = streaming_length
+ while data_length > 0
+ chunk_size = data_length > 10 ? 10 : data_length
+ stream_handler.handle_chunk(double("Chunk", :bytesize => chunk_size))
+ data_length -= chunk_size
+ end
+
+ # Finally call stream complete
+ middleware.handle_stream_complete(response, request, return_value)
+ when :direct
+ middleware.handle_response(response, request, return_value)
+ else
+ raise "Unknown request_type: #{request_type}"
+ end
+ end
+
+ let(:debug_stream) { StringIO.new }
+ let(:debug_output) { debug_stream.string }
+
+ before(:each) {
+ Chef::Log.level = :debug
+ Chef::Log.stub(:debug) do |message|
+ debug_stream.puts message
+ end
+ }
+
+ describe "without response body" do
+ let(:request_type) { :direct }
+ let(:response_body) { "Thanks for checking in." }
+
+ it "shouldn't raise error" do
+ lambda { run_content_length_validation }.should_not raise_error
+ end
+ end
+
+ describe "without Content-Length header" do
+ let(:response_headers) { { } }
+
+ [ "direct", "streaming" ].each do |req_type|
+ describe "when running #{req_type} request" do
+ let(:request_type) { req_type.to_sym }
+
+ it "should skip validation and log for debug" do
+ run_content_length_validation
+ debug_output.should include("HTTP server did not include a Content-Length header in response")
+ end
+ end
+ end
+ end
+
+ describe "with correct Content-Length header" do
+ [ "direct", "streaming" ].each do |req_type|
+ describe "when running #{req_type} request" do
+ let(:request_type) { req_type.to_sym }
+
+ it "should validate correctly" do
+ run_content_length_validation
+ debug_output.should include("Content-Length validated correctly.")
+ end
+ end
+ end
+ end
+
+ describe "with wrong Content-Length header" do
+ let(:content_length_value) { 25 }
+ [ "direct", "streaming" ].each do |req_type|
+ describe "when running #{req_type} request" do
+ let(:request_type) { req_type.to_sym }
+
+ it "should raise ContentLengthMismatch error" do
+ lambda { run_content_length_validation }.should raise_error(Chef::Exceptions::ContentLengthMismatch)
+ end
+ end
+ end
+ end
+
+ describe "when download is interrupted" do
+ let(:streaming_length) { 12 }
+
+ it "should raise ContentLengthMismatch error" do
+ lambda { run_content_length_validation }.should raise_error(Chef::Exceptions::ContentLengthMismatch)
+ end
+ end
+
+ describe "when Transfer-Encoding & Content-Length is set" do
+ let(:response_headers) {
+ {
+ "content-length" => content_length_value,
+ "transfer-encoding" => "chunked"
+ }
+ }
+
+ [ "direct", "streaming" ].each do |req_type|
+ describe "when running #{req_type} request" do
+ let(:request_type) { req_type.to_sym }
+
+ it "should skip validation and log for debug" do
+ run_content_length_validation
+ debug_output.should include("Transfer-Encoding header is set, skipping Content-Length check.")
+ end
+ end
+ end
+ end
+
+ describe "when client is being reused" do
+ before do
+ run_content_length_validation
+ debug_output.should include("Content-Length validated correctly.")
+ end
+
+ it "should reset internal counter" do
+ middleware.instance_variable_get(:@content_length_counter).should be_nil
+ end
+
+ it "should validate correctly second time" do
+ run_content_length_validation
+ debug_output.should include("Content-Length validated correctly.")
+ end
+ end
+
+end
diff --git a/spec/unit/knife/bootstrap_spec.rb b/spec/unit/knife/bootstrap_spec.rb
index b055cadcee..501e678a2b 100644
--- a/spec/unit/knife/bootstrap_spec.rb
+++ b/spec/unit/knife/bootstrap_spec.rb
@@ -124,12 +124,21 @@ describe Chef::Knife::Bootstrap do
end
describe "specifying no_proxy with various entries" do
- subject(:knife) { described_class.new }
- let(:options){ ["--bootstrap-no-proxy", setting] }
+ subject(:knife) do
+ k = described_class.new
+ k.instance_variable_set("@template_file", template_file)
+ k.parse_options(options)
+ k.merge_configs
+ k
+ end
+
+ # Include a data bag secret in the options to prevent Bootstrap from
+ # attempting to access /etc/chef/encrypted_data_bag_secret, which
+ # can fail when the file exists but can't be accessed by the user
+ # running the tests.
+ let(:options){ ["--bootstrap-no-proxy", setting, "-s", "foo"] }
let(:template_file) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "no_proxy.erb")) }
let(:rendered_template) do
- knife.instance_variable_set("@template_file", template_file)
- knife.parse_options(options)
template_string = knife.read_template
knife.render_template(template_string)
end
diff --git a/spec/unit/knife/client_bulk_delete_spec.rb b/spec/unit/knife/client_bulk_delete_spec.rb
index bedd4911c5..7df7d02e9b 100644
--- a/spec/unit/knife/client_bulk_delete_spec.rb
+++ b/spec/unit/knife/client_bulk_delete_spec.rb
@@ -19,60 +19,145 @@
require 'spec_helper'
describe Chef::Knife::ClientBulkDelete do
- before(:each) do
- Chef::Log.logger = Logger.new(StringIO.new)
-
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::ClientBulkDelete.new
- @knife.name_args = ["."]
- @stdout = StringIO.new
- @knife.ui.stub(:stdout).and_return(@stdout)
- @knife.ui.stub(:confirm).and_return(true)
- @clients = Hash.new
- %w{tim dan stephen}.each do |client_name|
+ let(:stdout_io) { StringIO.new }
+ let(:stdout) {stdout_io.string}
+
+ let(:knife) {
+ k = Chef::Knife::ClientBulkDelete.new
+ k.name_args = name_args
+ k.config = option_args
+ k.ui.stub(:stdout).and_return(stdout_io)
+ k.ui.stub(:confirm).and_return(knife_confirm)
+ k.ui.stub(:confirm_without_exit).and_return(knife_confirm)
+ k
+ }
+
+ let(:name_args) { [ "." ] }
+ let(:option_args) { {} }
+
+ let(:knife_confirm) { true }
+
+ let(:nonvalidator_client_names) { %w{tim dan stephen} }
+ let(:nonvalidator_clients) {
+ clients = Hash.new
+
+ nonvalidator_client_names.each do |client_name|
client = Chef::ApiClient.new()
client.name(client_name)
client.stub(:destroy).and_return(true)
- @clients[client_name] = client
+ clients[client_name] = client
+ end
+
+ clients
+ }
+
+ let(:validator_client_names) { %w{myorg-validator} }
+ let(:validator_clients) {
+ clients = Hash.new
+
+ validator_client_names.each do |validator_client_name|
+ validator_client = Chef::ApiClient.new()
+ validator_client.name(validator_client_name)
+ validator_client.stub(:validator).and_return(true)
+ validator_client.stub(:destroy).and_return(true)
+ clients[validator_client_name] = validator_client
end
- Chef::ApiClient.stub(:list).and_return(@clients)
+
+ clients
+ }
+
+ let(:client_names) { nonvalidator_client_names + validator_client_names}
+ let(:clients) {
+ nonvalidator_clients.merge(validator_clients)
+ }
+
+ before(:each) do
+ Chef::ApiClient.stub(:list).and_return(clients)
end
describe "run" do
+ describe "without a regex" do
+ let(:name_args) { [ ] }
- it "should get the list of the clients" do
- Chef::ApiClient.should_receive(:list).and_return(@clients)
- @knife.run
+ it "should exit if the regex is not provided" do
+ lambda { knife.run }.should raise_error(SystemExit)
+ end
end
- it "should print the clients you are about to delete" do
- @knife.run
- @stdout.string.should match(/#{@knife.ui.list(@clients.keys.sort, :columns_down)}/)
- end
+ describe "with any clients" do
+ it "should get the list of the clients" do
+ Chef::ApiClient.should_receive(:list)
+ knife.run
+ end
- it "should confirm you really want to delete them" do
- @knife.ui.should_receive(:confirm)
- @knife.run
- end
+ it "should print the name of the clients" do
+ knife.run
+ client_names.each do |client_name|
+ stdout.should include(client_name)
+ end
+ end
- it "should delete each client" do
- @clients.each_value do |c|
- c.should_receive(:destroy)
+ it "should confirm you really want to delete them" do
+ knife.ui.should_receive(:confirm)
+ knife.run
end
- @knife.run
- end
- it "should only delete clients that match the regex" do
- @knife.name_args = ["tim"]
- @clients["tim"].should_receive(:destroy)
- @clients["stephen"].should_not_receive(:destroy)
- @clients["dan"].should_not_receive(:destroy)
- @knife.run
+ describe "without --delete-validators" do
+ it "should mention that validator clients wont be deleted" do
+ knife.run
+ stdout.should include("Following clients are validators and will not be deleted.")
+ info = stdout.index "Following clients are validators and will not be deleted."
+ val = stdout.index "myorg-validator"
+ (val > info).should be_true
+ end
+
+ it "should only delete nonvalidator clients" do
+ nonvalidator_clients.each_value do |c|
+ c.should_receive(:destroy)
+ end
+
+ validator_clients.each_value do |c|
+ c.should_not_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
+ stdout.should include("The following validators will be deleted")
+ end
+
+ it "should confirm twice" do
+ knife.ui.should_receive(:confirm).once
+ knife.ui.should_receive(:confirm_without_exit).once
+ knife.run
+ end
+
+ it "should delete all clients" do
+ clients.each_value do |c|
+ c.should_receive(:destroy)
+ end
+
+ knife.run
+ end
+ end
end
- it "should exit if the regex is not provided" do
- @knife.name_args = []
- lambda { @knife.run }.should raise_error(SystemExit)
+ describe "with some clients" do
+ let(:name_args) { [ "^ti" ] }
+
+ it "should only delete clients that match the regex" do
+ clients["tim"].should_receive(:destroy)
+ clients["stephen"].should_not_receive(:destroy)
+ clients["dan"].should_not_receive(:destroy)
+ clients["myorg-validator"].should_not_receive(:destroy)
+ knife.run
+ end
end
end
end
diff --git a/spec/unit/knife/client_create_spec.rb b/spec/unit/knife/client_create_spec.rb
index 69c55ba015..897cee8974 100644
--- a/spec/unit/knife/client_create_spec.rb
+++ b/spec/unit/knife/client_create_spec.rb
@@ -25,7 +25,9 @@ describe Chef::Knife::ClientCreate do
Chef::Config[:node_name] = "webmonkey.example.com"
@knife = Chef::Knife::ClientCreate.new
@knife.config = {
- :file => nil
+ :file => nil,
+ :admin => false,
+ :validator => false
}
@knife.name_args = [ "adam" ]
@client = Chef::ApiClient.new
@@ -49,6 +51,16 @@ describe Chef::Knife::ClientCreate do
@knife.run
end
+ it "by default it is not an admin" do
+ @client.should_receive(:admin).with(false)
+ @knife.run
+ end
+
+ it "by default it is not a validator" do
+ @client.should_receive(:validator).with(false)
+ @knife.run
+ end
+
it "should allow you to edit the data" do
@knife.should_receive(:edit_data).with(@client)
@knife.run
@@ -70,5 +82,21 @@ describe Chef::Knife::ClientCreate do
end
end
+ describe "with -a or --admin" do
+ it "should create an admin client" do
+ @knife.config[:admin] = true
+ @client.should_receive(:admin).with(true)
+ @knife.run
+ end
+ end
+
+ describe "with --validator" do
+ it "should create an validator client" do
+ @knife.config[:validator] = true
+ @client.should_receive(:validator).with(true)
+ @knife.run
+ end
+ end
+
end
end
diff --git a/spec/unit/knife/client_delete_spec.rb b/spec/unit/knife/client_delete_spec.rb
index 9ebccbae15..01b49b3d7c 100644
--- a/spec/unit/knife/client_delete_spec.rb
+++ b/spec/unit/knife/client_delete_spec.rb
@@ -21,12 +21,16 @@ require '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
- @knife.should_receive(:delete_object).with(Chef::ApiClient, 'adam')
+ @knife.should_receive(:delete_object).with(Chef::ApiClient, 'adam', 'client')
@knife.run
end
@@ -37,4 +41,43 @@ describe Chef::Knife::ClientDelete do
lambda { @knife.run }.should raise_error(SystemExit)
end
end
+
+ describe 'with a validator' do
+ before(:each) do
+ Chef::Knife::UI.stub(:confirm).and_return(true)
+ @knife.stub(:confirm).and_return(true)
+ @client = Chef::ApiClient.new
+ Chef::ApiClient.should_receive(:load).and_return(@client)
+ end
+
+ it 'should delete non-validator client if --force is not set' do
+ @knife.config[:delete_validators] = false
+ @client.should_receive(:destroy).and_return(@client)
+ @knife.should_receive(:msg)
+
+ @knife.run
+ end
+
+ it 'should delete non-validator client if --force is set' do
+ @knife.config[:delete_validators] = true
+ @client.should_receive(:destroy).and_return(@client)
+ @knife.should_receive(:msg)
+
+ @knife.run
+ end
+
+ it 'should not delete validator client if --force is not set' do
+ @client.validator(true)
+ @knife.ui.should_receive(:fatal)
+ lambda { @knife.run}.should raise_error(SystemExit)
+ end
+
+ it 'should delete validator client if --force is set' do
+ @knife.config[:delete_validators] = true
+ @client.should_receive(:destroy).and_return(@client)
+ @knife.should_receive(:msg)
+
+ @knife.run
+ end
+ end
end
diff --git a/spec/unit/knife/cookbook_upload_spec.rb b/spec/unit/knife/cookbook_upload_spec.rb
index 65331b3952..5c7a4c1125 100644
--- a/spec/unit/knife/cookbook_upload_spec.rb
+++ b/spec/unit/knife/cookbook_upload_spec.rb
@@ -23,178 +23,271 @@ require 'chef/cookbook_uploader'
require 'timeout'
describe Chef::Knife::CookbookUpload do
- before(:each) do
- @knife = Chef::Knife::CookbookUpload.new
- @knife.name_args = ['test_cookbook']
+ let(:cookbook) { Chef::CookbookVersion.new('test_cookbook') }
+
+ let(:cookbooks_by_name) do
+ {cookbook.name => cookbook}
+ end
- @cookbook = Chef::CookbookVersion.new('test_cookbook')
+ let(:cookbook_loader) do
+ cookbook_loader = cookbooks_by_name.dup
+ cookbook_loader.stub(:merged_cookbooks).and_return([])
+ cookbook_loader.stub(:load_cookbooks).and_return(cookbook_loader)
+ cookbook_loader
+ end
+
+ let(:cookbook_uploader) { double(:upload_cookbooks => nil) }
- @cookbook_loader = {}
- @cookbook_loader.stub(:[]).and_return(@cookbook)
- @cookbook_loader.stub(:merged_cookbooks).and_return([])
- @cookbook_loader.stub(:load_cookbooks).and_return(@cookbook_loader)
- Chef::CookbookLoader.stub(:new).and_return(@cookbook_loader)
+ let(:output) { StringIO.new }
+
+ let(:name_args) { ['test_cookbook'] }
- @output = StringIO.new
- @knife.ui.stub(:stdout).and_return(@output)
- @knife.ui.stub(:stderr).and_return(@output)
+ let(:knife) do
+ k = Chef::Knife::CookbookUpload.new
+ k.name_args = name_args
+ k.ui.stub(:stdout).and_return(output)
+ k.ui.stub(:stderr).and_return(output)
+ k
+ end
+
+ before(:each) do
+ Chef::CookbookLoader.stub(:new).and_return(cookbook_loader)
end
describe 'with --concurrency' do
it 'should upload cookbooks with predefined concurrency' do
- @cookbook_uploader = double(:upload_cookbooks => nil)
Chef::CookbookVersion.stub(:list_all_versions).and_return({})
- @knife.config[:concurrency] = 3
- @test_cookbook = Chef::CookbookVersion.new('test_cookbook')
- @cookbook_loader.stub(:each).and_yield("test_cookbook", @test_cookbook)
- @cookbook_loader.stub(:cookbook_names).and_return(["test_cookbook"])
+ knife.config[:concurrency] = 3
+ test_cookbook = Chef::CookbookVersion.new('test_cookbook')
+ cookbook_loader.stub(:each).and_yield("test_cookbook", test_cookbook)
+ cookbook_loader.stub(:cookbook_names).and_return(["test_cookbook"])
Chef::CookbookUploader.should_receive(:new).with( kind_of(Array), kind_of(Array),
{:force=>nil, :concurrency => 3}).and_return(double("Chef::CookbookUploader", :upload_cookbooks=> true))
- @knife.run
+ knife.run
end
end
describe 'run' do
before(:each) do
- @cookbook_uploader = double(:upload_cookbooks => nil)
- Chef::CookbookUploader.stub(:new => @cookbook_uploader)
+ Chef::CookbookUploader.stub(:new => cookbook_uploader)
Chef::CookbookVersion.stub(:list_all_versions).and_return({})
end
it 'should print usage and exit when a cookbook name is not provided' do
- @knife.name_args = []
- @knife.should_receive(:show_usage)
- @knife.ui.should_receive(:fatal)
- lambda { @knife.run }.should raise_error(SystemExit)
+ knife.name_args = []
+ knife.should_receive(:show_usage)
+ knife.ui.should_receive(:fatal)
+ lambda { knife.run }.should raise_error(SystemExit)
end
describe 'when specifying a cookbook name' do
it 'should upload the cookbook' do
- @knife.should_receive(:upload).once
- @knife.run
+ knife.should_receive(:upload).once
+ knife.run
end
it 'should report on success' do
- @knife.should_receive(:upload).once
- @knife.ui.should_receive(:info).with(/Uploaded 1 cookbook/)
- @knife.run
+ knife.should_receive(:upload).once
+ knife.ui.should_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 = ['test_cookbook', 'test_cookbook']
- @knife.should_receive(:upload).once
- @knife.run
+ knife.name_args = ['test_cookbook', 'test_cookbook']
+ knife.should_receive(:upload).once
+ knife.run
+ end
+ end
+
+ context "when uploading a cookbook that uses deprecated overlays" do
+
+ before do
+ cookbook_loader.stub(:merged_cookbooks).and_return(['test_cookbook'])
+ cookbook_loader.stub(:merged_cookbook_paths).
+ and_return({'test_cookbook' => %w{/path/one/test_cookbook /path/two/test_cookbook}})
+ end
+
+ it "emits a warning" do
+ knife.run
+ expected_message=<<-E
+WARNING: The cookbooks: test_cookbook exist in multiple places in your cookbook_path.
+A composite version of these cookbooks has been compiled for uploading.
+
+IMPORTANT: In a future version of Chef, this behavior will be removed and you will no longer
+be able to have the same version of a cookbook in multiple places in your cookbook_path.
+WARNING: The affected cookbooks are located:
+test_cookbook:
+ /path/one/test_cookbook
+ /path/two/test_cookbook
+E
+ output.string.should include(expected_message)
end
end
describe 'when specifying a cookbook name among many' do
- before(:each) do
- @knife.name_args = ['test_cookbook1']
- @cookbooks = {
+ let(:name_args) { ['test_cookbook1'] }
+
+ let(:cookbooks_by_name) do
+ {
'test_cookbook1' => Chef::CookbookVersion.new('test_cookbook1'),
'test_cookbook2' => Chef::CookbookVersion.new('test_cookbook2'),
'test_cookbook3' => Chef::CookbookVersion.new('test_cookbook3')
}
- @cookbook_loader = {}
- @cookbook_loader.stub(:merged_cookbooks).and_return([])
- @cookbook_loader.stub(:[]) { |ckbk| @cookbooks[ckbk] }
- Chef::CookbookLoader.stub(:new).and_return(@cookbook_loader)
end
it "should read only one cookbook" do
- @cookbook_loader.should_receive(:[]).once.with('test_cookbook1')
- @knife.run
+ cookbook_loader.should_receive(:[]).once.with('test_cookbook1').and_call_original
+ knife.run
end
it "should not read all cookbooks" do
- @cookbook_loader.should_not_receive(:load_cookbooks)
- @knife.run
+ cookbook_loader.should_not_receive(:load_cookbooks)
+ knife.run
end
it "should upload only one cookbook" do
- @knife.should_receive(:upload).exactly(1).times
- @knife.run
+ knife.should_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(:cookbooks_by_name) do
+ { "test_cookbook1" => test_cookbook1,
+ "test_cookbook2" => test_cookbook2,
+ "test_cookbook3" => test_cookbook3 }
+ end
+
+ let(:test_cookbook1) { Chef::CookbookVersion.new('test_cookbook1') }
+
+ let(:test_cookbook2) do
+ c = Chef::CookbookVersion.new('test_cookbook2')
+ c.metadata.depends("test_cookbook3")
+ c
+ end
+
+ let(:test_cookbook3) do
+ c = Chef::CookbookVersion.new('test_cookbook3')
+ c.metadata.depends("test_cookbook1")
+ c.metadata.depends("test_cookbook2")
+ c
+ end
+
it "should upload all dependencies once" do
- @knife.name_args = ["test_cookbook2"]
- @knife.config[:depends] = true
- @test_cookbook1 = Chef::CookbookVersion.new('test_cookbook1')
- @test_cookbook2 = Chef::CookbookVersion.new('test_cookbook2')
- @test_cookbook3 = Chef::CookbookVersion.new('test_cookbook3')
- @test_cookbook2.metadata.depends("test_cookbook3")
- @test_cookbook3.metadata.depends("test_cookbook1")
- @test_cookbook3.metadata.depends("test_cookbook2")
- @cookbook_loader.stub(:[]) do |ckbk|
- { "test_cookbook1" => @test_cookbook1,
- "test_cookbook2" => @test_cookbook2,
- "test_cookbook3" => @test_cookbook3 }[ckbk]
- end
- @knife.stub(:cookbook_names).and_return(["test_cookbook1", "test_cookbook2", "test_cookbook3"])
- @knife.should_receive(:upload).exactly(3).times
- Timeout::timeout(5) do
- @knife.run
+ knife.config[:depends] = true
+ knife.stub(:cookbook_names).and_return(["test_cookbook1", "test_cookbook2", "test_cookbook3"])
+ knife.should_receive(:upload).exactly(3).times
+ lambda do
+ Timeout::timeout(5) do
+ knife.run
+ end
end.should_not raise_error
end
end
+ describe 'when specifying a cookbook name with missing dependencies' do
+ let(:cookbook_dependency) { Chef::CookbookVersion.new('dependency') }
+
+ before(:each) do
+ cookbook.metadata.depends("dependency")
+ cookbook_loader.stub(:[]) do |ckbk|
+ { "test_cookbook" => cookbook,
+ "dependency" => cookbook_dependency}[ckbk]
+ end
+ knife.stub(:cookbook_names).and_return(["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
+ cookbook_loader.should_receive(:[]).once.with('test_cookbook')
+ cookbook_loader.should_not_receive(:load_cookbooks)
+ cookbook_uploader.should_not_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)
+ @stderr.string.should include('Cookbook test_cookbook depends on cookbooks which are not currently')
+ @stderr.string.should include('being uploaded and cannot be found on the server.')
+ @stderr.string.should 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")
+ cookbook_loader.stub(:[]) do |ckbk|
+ { "test_cookbook" => cookbook,
+ "dependency" => cookbook_dependency,
+ "dependency2" => cookbook_dependency2}[ckbk]
+ end
+ knife.stub(:cookbook_names).and_return(["dependency", "dependency2", "test_cookbook"])
+ expect {knife.run}.to raise_error(SystemExit)
+ @stderr.string.should include('Cookbook test_cookbook depends on cookbooks which are not currently')
+ @stderr.string.should include('being uploaded and cannot be found on the server.')
+ @stderr.string.should include("The missing cookbook(s) are:")
+ @stderr.string.should include("'dependency' version '>= 0.0.0'")
+ @stderr.string.should 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
- @cookbook.should_receive(:freeze_version).once
- @knife.run
+ knife.config[:freeze] = true
+ cookbook.should_receive(:freeze_version).once
+ knife.run
end
describe 'with -a or --all' do
before(:each) do
- @knife.config[:all] = true
+ knife.config[:all] = true
@test_cookbook1 = Chef::CookbookVersion.new('test_cookbook1')
@test_cookbook2 = Chef::CookbookVersion.new('test_cookbook2')
- @cookbook_loader.stub(:each).and_yield("test_cookbook1", @test_cookbook1).and_yield("test_cookbook2", @test_cookbook2)
- @cookbook_loader.stub(:cookbook_names).and_return(["test_cookbook1", "test_cookbook2"])
+ cookbook_loader.stub(:each).and_yield("test_cookbook1", @test_cookbook1).and_yield("test_cookbook2", @test_cookbook2)
+ cookbook_loader.stub(:cookbook_names).and_return(["test_cookbook1", "test_cookbook2"])
end
it 'should upload all cookbooks' do
- @knife.should_receive(:upload).once
- @knife.run
+ knife.should_receive(:upload).once
+ knife.run
end
it 'should report on success' do
- @knife.should_receive(:upload).once
- @knife.ui.should_receive(:info).with(/Uploaded all cookbooks/)
- @knife.run
+ knife.should_receive(:upload).once
+ knife.ui.should_receive(:info).with(/Uploaded all cookbooks/)
+ knife.run
end
it 'should update the version constraints for an environment' do
- @knife.stub(:assert_environment_valid!).and_return(true)
- @knife.config[:environment] = "production"
- @knife.should_receive(:update_version_constraints).once
- @knife.run
+ knife.stub(:assert_environment_valid!).and_return(true)
+ knife.config[:environment] = "production"
+ knife.should_receive(:update_version_constraints).once
+ knife.run
end
end
describe 'when a frozen cookbook exists on the server' do
it 'should fail to replace it' do
exception = Chef::Exceptions::CookbookFrozen.new
- @cookbook_uploader.should_receive(:upload_cookbooks).
+ cookbook_uploader.should_receive(:upload_cookbooks).
and_raise(exception)
- @knife.ui.stub(:error)
- @knife.ui.should_receive(:error).with(exception)
- lambda { @knife.run }.should raise_error(SystemExit)
+ knife.ui.stub(:error)
+ knife.ui.should_receive(:error).with(exception)
+ lambda { knife.run }.should raise_error(SystemExit)
end
it 'should not update the version constraints for an environment' do
- @knife.stub(:assert_environment_valid!).and_return(true)
- @knife.config[:environment] = "production"
- @knife.stub(:upload).and_raise(Chef::Exceptions::CookbookFrozen)
- @knife.ui.should_receive(:error).with(/Failed to upload 1 cookbook/)
- @knife.ui.should_receive(:warn).with(/Not updating version constraints/)
- @knife.should_not_receive(:update_version_constraints)
- lambda { @knife.run }.should raise_error(SystemExit)
+ knife.stub(:assert_environment_valid!).and_return(true)
+ knife.config[:environment] = "production"
+ knife.stub(:upload).and_raise(Chef::Exceptions::CookbookFrozen)
+ knife.ui.should_receive(:error).with(/Failed to upload 1 cookbook/)
+ knife.ui.should_receive(:warn).with(/Not updating version constraints/)
+ knife.should_not_receive(:update_version_constraints)
+ lambda { knife.run }.should raise_error(SystemExit)
end
end
end # run
diff --git a/spec/unit/knife/core/bootstrap_context_spec.rb b/spec/unit/knife/core/bootstrap_context_spec.rb
index 47261e2068..17e265edad 100644
--- a/spec/unit/knife/core/bootstrap_context_spec.rb
+++ b/spec/unit/knife/core/bootstrap_context_spec.rb
@@ -41,13 +41,19 @@ describe Chef::Knife::Core::BootstrapContext do
bootstrap_context.start_chef.should eq "chef-client -j /etc/chef/first-boot.json -E _default"
end
+ describe "when in verbosity mode" do
+ let(:config) { {:verbosity => 2} }
+ it "adds '-l debug' when verbosity is >= 2" do
+ bootstrap_context.start_chef.should eq "chef-client -j /etc/chef/first-boot.json -l debug -E _default"
+ end
+ end
+
it "reads the validation key" do
bootstrap_context.validation_key.should eq IO.read(File.join(CHEF_SPEC_DATA, 'ssl', 'private_key.pem'))
end
it "generates the config file data" do
expected=<<-EXPECTED
-log_level :auto
log_location STDOUT
chef_server_url "http://chef.example.com:4444"
validation_client_name "chef-validator-testing"
@@ -56,6 +62,10 @@ EXPECTED
bootstrap_context.config_content.should eq expected
end
+ it "does not set a default log_level" do
+ expect(bootstrap_context.config_content).not_to match(/log_level/)
+ 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
diff --git a/spec/unit/knife/core/ui_spec.rb b/spec/unit/knife/core/ui_spec.rb
index c626747918..9044bc2f2f 100644
--- a/spec/unit/knife/core/ui_spec.rb
+++ b/spec/unit/knife/core/ui_spec.rb
@@ -406,61 +406,132 @@ EOM
end
describe "confirm" do
- before(:each) do
- @question = "monkeys rule"
- @stdout = StringIO.new
- @ui.stub(:stdout).and_return(@stdout)
- @ui.stdin.stub(:readline).and_return("y")
+ 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
+ @ui.stub(:stdout).and_return(stdout)
+ @ui.stdin.stub(:readline).and_return(answer)
+ @ui.confirm(question, append_instructions, default_choice)
end
- it "should return true if you answer Y" do
- @ui.stdin.stub(:readline).and_return("Y")
- @ui.confirm(@question).should == true
+ def run_confirm_without_exit
+ @ui.stub(:stdout).and_return(stdout)
+ @ui.stdin.stub(:readline).and_return(answer)
+ @ui.confirm_without_exit(question, append_instructions, default_choice)
end
- it "should return true if you answer y" do
- @ui.stdin.stub(:readline).and_return("y")
- @ui.confirm(@question).should == true
+ shared_examples_for "confirm with positive answer" do
+ it "confirm should return true" do
+ run_confirm.should be_true
+ end
+
+ it "confirm_without_exit should return true" do
+ run_confirm_without_exit.should be_true
+ end
end
- it "should exit 3 if you answer N" do
- @ui.stdin.stub(:readline).and_return("N")
- lambda {
- @ui.confirm(@question)
- }.should raise_error(SystemExit) { |e| e.status.should == 3 }
+ shared_examples_for "confirm with negative answer" do
+ it "confirm should exit 3" do
+ lambda {
+ run_confirm
+ }.should raise_error(SystemExit) { |e| e.status.should == 3 }
+ end
+
+ it "confirm_without_exit should return false" do
+ run_confirm_without_exit.should be_false
+ end
end
- it "should exit 3 if you answer n" do
- @ui.stdin.stub(:readline).and_return("n")
- lambda {
- @ui.confirm(@question)
- }.should raise_error(SystemExit) { |e| e.status.should == 3 }
+ describe "with default choice set to true" do
+ let(:default_choice) { true }
+
+ it "should show 'Y/n' in the instructions" do
+ run_confirm
+ output.should 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 --y or --yes passed" do
- it "should return true" do
- @ui.config[:yes] = true
- @ui.confirm(@question).should == true
+ describe "with default choice set to false" do
+ let(:default_choice) { false }
+
+ it "should show 'y/N' in the instructions" do
+ run_confirm
+ output.should 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
- 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
- @ui.stub(:stdout).and_return(out)
- @ui.stub(:stdin).and_return(StringIO.new("http://mychefserver.example.com\n"))
- @ui.ask_question("your chef server URL?").should == "http://mychefserver.example.com"
- out.string.should == "your chef server URL?"
+ ["Y", "y"].each do |answer|
+ describe "with answer #{answer}" do
+ let(:answer) { answer }
+
+ it_behaves_like "confirm with positive answer"
end
+ end
- it "suggests a default setting and returns the default when the user's response only contains whitespace" do
- out = StringIO.new
- @ui.stub(:stdout).and_return(out)
- @ui.stub(:stdin).and_return(StringIO.new(" \n"))
- @ui.ask_question("your chef server URL? ", :default => 'http://localhost:4000').should == "http://localhost:4000"
- out.string.should == "your chef server URL? [http://localhost:4000] "
+ ["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
+ run_confirm.should be_true
+ output.should 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
+ @ui.stub(:stdout).and_return(out)
+ @ui.stub(:stdin).and_return(StringIO.new("http://mychefserver.example.com\n"))
+ @ui.ask_question("your chef server URL?").should == "http://mychefserver.example.com"
+ out.string.should == "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
+ @ui.stub(:stdout).and_return(out)
+ @ui.stub(:stdin).and_return(StringIO.new(" \n"))
+ @ui.ask_question("your chef server URL? ", :default => 'http://localhost:4000').should == "http://localhost:4000"
+ out.string.should == "your chef server URL? [http://localhost:4000] "
+ end
end
+
end
diff --git a/spec/unit/knife/node_run_list_add_spec.rb b/spec/unit/knife/node_run_list_add_spec.rb
index 61f2e6af4c..bd33a359a2 100644
--- a/spec/unit/knife/node_run_list_add_spec.rb
+++ b/spec/unit/knife/node_run_list_add_spec.rb
@@ -65,6 +65,29 @@ describe Chef::Knife::NodeRunListAdd do
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
+ @node.run_list[0].should == "role[monkey]"
+ @node.run_list[1].should == "role[acorns]"
+ @node.run_list[2].should == "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]"
+ @knife.ui.should_receive(:fatal)
+ lambda { @knife.run }.should 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]" ]
@@ -98,7 +121,7 @@ describe Chef::Knife::NodeRunListAdd do
end
end
- describe "with more than one role or recipe as different arguments and list separated by comas" do
+ 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]"
diff --git a/spec/unit/knife/ssh_spec.rb b/spec/unit/knife/ssh_spec.rb
index eff7c9ba5b..9247db3c90 100644
--- a/spec/unit/knife/ssh_spec.rb
+++ b/spec/unit/knife/ssh_spec.rb
@@ -54,7 +54,7 @@ describe Chef::Knife::Ssh do
@knife.config[:attribute] = "ipaddress"
@knife.config[:override_attribute] = "ipaddress"
configure_query([@node_foo, @node_bar])
- @knife.should_receive(:session_from_list).with(['10.0.0.1', '10.0.0.2'])
+ @knife.should_receive(:session_from_list).with([['10.0.0.1', nil], ['10.0.0.2', nil]])
@knife.configure_session
end
@@ -62,14 +62,17 @@ describe Chef::Knife::Ssh do
@knife.config[:attribute] = "config_file" # this value will be the config file
@knife.config[:override_attribute] = "ipaddress" # this is the value of the command line via #configure_attribute
configure_query([@node_foo, @node_bar])
- @knife.should_receive(:session_from_list).with(['10.0.0.1', '10.0.0.2'])
+ @knife.should_receive(:session_from_list).with([['10.0.0.1', nil], ['10.0.0.2', nil]])
@knife.configure_session
end
end
it "searchs for and returns an array of fqdns" do
configure_query([@node_foo, @node_bar])
- @knife.should_receive(:session_from_list).with(['foo.example.org', 'bar.example.org'])
+ @knife.should_receive(:session_from_list).with([
+ ['foo.example.org', nil],
+ ['bar.example.org', nil]
+ ])
@knife.configure_session
end
@@ -83,7 +86,10 @@ describe Chef::Knife::Ssh do
it "returns an array of cloud public hostnames" do
configure_query([@node_foo, @node_bar])
- @knife.should_receive(:session_from_list).with(['ec2-10-0-0-1.compute-1.amazonaws.com', 'ec2-10-0-0-2.compute-1.amazonaws.com'])
+ @knife.should_receive(:session_from_list).with([
+ ['ec2-10-0-0-1.compute-1.amazonaws.com', nil],
+ ['ec2-10-0-0-2.compute-1.amazonaws.com', nil]
+ ])
@knife.configure_session
end
@@ -179,12 +185,17 @@ describe Chef::Knife::Ssh do
end
it "uses the port from an ssh config file" do
- @knife.session_from_list(['the.b.org'])
+ @knife.session_from_list([['the.b.org', nil]])
@knife.session.servers[0].port.should == 23
end
+ it "uses the port from a cloud attr" do
+ @knife.session_from_list([['the.b.org', 123]])
+ @knife.session.servers[0].port.should == 123
+ end
+
it "uses the user from an ssh config file" do
- @knife.session_from_list(['the.b.org'])
+ @knife.session_from_list([['the.b.org', 123]])
@knife.session.servers[0].user.should == "locutus"
end
end
diff --git a/spec/unit/knife/ssl_check_spec.rb b/spec/unit/knife/ssl_check_spec.rb
new file mode 100644
index 0000000000..32405a5977
--- /dev/null
+++ b/spec/unit/knife/ssl_check_spec.rb
@@ -0,0 +1,187 @@
+#
+# Author:: Daniel DeLeo (<dan@getchef.com>)
+# Copyright:: Copyright (c) 2014 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"
+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
+ s.ui.stub(:stdout).and_return(stdout_io)
+ s.ui.stub(: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 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
+ TCPSocket.should_receive(:new).with("foo.example.com", 8443).and_return(tcp_socket)
+ OpenSSL::SSL::SSLSocket.should_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
+ ssl_socket.should_receive(:connect) # no error
+ ssl_socket.should_receive(:post_connection_check).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
+ trap(:INT, "DEFAULT")
+
+ TCPSocket.should_receive(:new).
+ with("foo.example.com", 8443).
+ and_return(tcp_socket_for_debug)
+ OpenSSL::SSL::SSLSocket.should_receive(:new).
+ with(tcp_socket_for_debug, ssl_check.noverify_peer_ssl_context).
+ and_return(ssl_socket_for_debug)
+ end
+
+ context "when the certificate's CN does not match the hostname" do
+ before do
+ ssl_socket.should_receive(:connect) # no error
+ ssl_socket.should_receive(:post_connection_check).
+ with("foo.example.com").
+ and_raise(OpenSSL::SSL::SSLError)
+ ssl_socket_for_debug.should_receive(:connect)
+ ssl_socket_for_debug.should_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
+ ssl_socket.should_receive(:connect).
+ and_raise(OpenSSL::SSL::SSLError)
+ ssl_socket_for_debug.should_receive(:connect)
+ ssl_socket_for_debug.should_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/spec/unit/knife/ssl_fetch_spec.rb b/spec/unit/knife/ssl_fetch_spec.rb
new file mode 100644
index 0000000000..0d3c8913f7
--- /dev/null
+++ b/spec/unit/knife/ssl_fetch_spec.rb
@@ -0,0 +1,151 @@
+#
+# Author:: Daniel DeLeo (<dan@getchef.com>)
+# Copyright:: Copyright (c) 2014 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'
+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
+ s.ui.stub(:stdout).and_return(stdout_io)
+ s.ui.stub(: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 "fetchs 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 "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
+
+ TCPSocket.should_receive(:new).with("foo.example.com", 8443).and_return(tcp_socket)
+ OpenSSL::SSL::SSLSocket.should_receive(:new).with(tcp_socket, ssl_fetch.noverify_peer_ssl_context).and_return(ssl_socket)
+ ssl_socket.should_receive(:connect)
+ ssl_socket.should_receive(:peer_cert_chain).and_return([self_signed_crt])
+ end
+
+ after do
+ FileUtils.rm_rf(trusted_certs_dir)
+ 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
+end
diff --git a/spec/unit/knife_spec.rb b/spec/unit/knife_spec.rb
index daace18106..8bf85bf604 100644
--- a/spec/unit/knife_spec.rb
+++ b/spec/unit/knife_spec.rb
@@ -22,6 +22,7 @@ module KnifeSpecs
end
require 'spec_helper'
+require 'uri'
describe Chef::Knife do
before(:each) do
@@ -141,6 +142,60 @@ describe Chef::Knife do
end
+ describe "the headers include X-Remote-Request-Id" do
+
+ let(:headers) {{"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:443",
+ "X-REMOTE-REQUEST-ID"=>request_id}}
+
+ let(:request_id) {"1234"}
+
+ let(:request_mock) { {} }
+
+ let(:rest) do
+ Net::HTTP.stub(:new).and_return(http_client)
+ Chef::RequestID.instance.stub(:request_id).and_return(request_id)
+ Chef::Config.stub(: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)
+ http_client.stub(: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")
+ http_response.stub(:read_body)
+ http_response.stub(: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.kind_of?(Class) }
+ end
+
+ it "confirms that the headers include X-Remote-Request-Id" do
+ Net::HTTP::Get.should_receive(:new).with("/monkey", headers).and_return(request_mock)
+ rest.get_rest("monkey")
+ end
+ end
+
describe "when running a command" do
before(:each) do
if KnifeSpecs.const_defined?(:TestYourself)
diff --git a/spec/unit/mixin/deep_merge_spec.rb b/spec/unit/mixin/deep_merge_spec.rb
index 0a7bbffa41..76f5c68a29 100644
--- a/spec/unit/mixin/deep_merge_spec.rb
+++ b/spec/unit/mixin/deep_merge_spec.rb
@@ -284,6 +284,10 @@ describe Chef::Mixin::DeepMerge do
ret.should == {"property" => ["1","2","3","4","5","6"]}
end
+ it "should not error merging un-dupable objects" do
+ @dm.deep_merge(nil, 4)
+ end
+
end
describe "role_merge" do
@@ -347,5 +351,18 @@ describe Chef::Mixin::DeepMerge do
merged_result["top_level_a"]["1_deep_b"].should == %w[B B B]
end
+ it "does not mutate deeply-nested original hashes by default" do
+ merge_ee_hash = {"top_level_a" => {"1_deep_a" => { "2_deep_a" => { "3_deep_a" => "foo" }}}}
+ merge_with_hash = {"top_level_a" => {"1_deep_a" => { "2_deep_a" => { "3_deep_b" => "bar" }}}}
+ @dm.hash_only_merge(merge_ee_hash, merge_with_hash)
+ merge_ee_hash.should == {"top_level_a" => {"1_deep_a" => { "2_deep_a" => { "3_deep_a" => "foo" }}}}
+ merge_with_hash.should == {"top_level_a" => {"1_deep_a" => { "2_deep_a" => { "3_deep_b" => "bar" }}}}
+ end
+
+ it "does not error merging un-dupable items" do
+ merge_ee_hash = {"top_level_a" => 1, "top_level_b" => false}
+ merge_with_hash = {"top_level_a" => 2, "top_level_b" => true }
+ @dm.hash_only_merge(merge_ee_hash, merge_with_hash)
+ end
end
end
diff --git a/spec/unit/node/attribute_spec.rb b/spec/unit/node/attribute_spec.rb
index ef3fc60cc6..bab2e33aa9 100644
--- a/spec/unit/node/attribute_spec.rb
+++ b/spec/unit/node/attribute_spec.rb
@@ -488,6 +488,13 @@ describe Chef::Node::Attribute do
end
end
+ describe "dup" do
+ it "array can be duped even if some elements can't" do
+ @attributes.default[:foo] = %w[foo bar baz] + Array(1..3) + [nil, true, false, [ "el", 0, nil ] ]
+ @attributes.default[:foo].dup
+ end
+ end
+
describe "has_key?" do
it "should return true if an attribute exists" do
@attributes.has_key?("music").should == true
diff --git a/spec/unit/node/immutable_collections_spec.rb b/spec/unit/node/immutable_collections_spec.rb
index 0c2b878cd2..d7abfa26e6 100644
--- a/spec/unit/node/immutable_collections_spec.rb
+++ b/spec/unit/node/immutable_collections_spec.rb
@@ -54,6 +54,32 @@ describe Chef::Node::ImmutableMash do
@immutable_mash[:top_level_4][:level2].should be_a(Chef::Node::ImmutableMash)
end
+ describe "to_hash" do
+ before do
+ @copy = @immutable_mash.to_hash
+ end
+
+ it "converts an immutable mash to a new mutable hash" do
+ @copy.should be_instance_of(Hash)
+ end
+
+ it "converts an immutable nested mash to a new mutable hash" do
+ @copy['top_level_4']['level2'].should be_instance_of(Hash)
+ end
+
+ it "converts an immutable nested array to a new mutable array" do
+ @copy['top_level_2'].should be_instance_of(Array)
+ end
+
+ it "should create a mash with the same content" do
+ @copy.should == @immutable_mash
+ end
+
+ it 'should allow mutation' do
+ lambda { @copy['m'] = 'm' }.should_not raise_error(Chef::Exceptions::ImmutableAttributeModification)
+ end
+
+ end
[
:[]=,
@@ -86,7 +112,9 @@ end
describe Chef::Node::ImmutableArray do
before do
- @immutable_array = Chef::Node::ImmutableArray.new(%w[foo bar baz])
+ @immutable_array = Chef::Node::ImmutableArray.new(%w[foo bar baz] + Array(1..3) + [nil, true, false, [ "el", 0, nil ] ])
+ immutable_mash = Chef::Node::ImmutableMash.new({:m => 'm'})
+ @immutable_nested_array = Chef::Node::ImmutableArray.new(["level1",@immutable_array, immutable_mash])
end
##
@@ -130,10 +158,41 @@ describe Chef::Node::ImmutableArray do
end
end
+ it "can be duped even if some elements can't" do
+ @immutable_array.dup
+ end
+
it "returns a mutable version of itself when duped" do
mutable = @immutable_array.dup
mutable[0] = :value
mutable[0].should == :value
end
+
+ describe "to_a" do
+ before do
+ @copy = @immutable_nested_array.to_a
+ end
+
+ it "converts an immutable array to a new mutable array" do
+ @copy.should be_instance_of(Array)
+ end
+
+ it "converts an immutable nested array to a new mutable array" do
+ @copy[1].should be_instance_of(Array)
+ end
+
+ it "converts an immutable nested mash to a new mutable hash" do
+ @copy[2].should be_instance_of(Hash)
+ end
+
+ it "should create an array with the same content" do
+ @copy.should == @immutable_nested_array
+ end
+
+ it 'should allow mutation' do
+ lambda { @copy << 'm' }.should_not raise_error(Chef::Exceptions::ImmutableAttributeModification)
+ end
+ end
+
end
diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb
index f2a78f87cd..832e10f645 100644
--- a/spec/unit/node_spec.rb
+++ b/spec/unit/node_spec.rb
@@ -724,6 +724,15 @@ describe Chef::Node do
json.should =~ /\"run_list\":\[\"role\[Cthulu\]\",\"role\[Hastur\]\"\]/
end
+ it "should serialize the correct run list", :json => true do
+ node.run_list << "role[marxist]"
+ node.run_list << "role[leninist]"
+ node.override_runlist << "role[stalinist]"
+ node.run_list.should be_include("role[stalinist]")
+ json = Chef::JSONCompat.to_json(node)
+ json.should =~ /\"run_list\":\[\"role\[marxist\]\",\"role\[leninist\]\"\]/
+ end
+
it "merges the override components into a combined override object" do
node.attributes.role_override["role override"] = "role override"
node.attributes.env_override["env override"] = "env override"
diff --git a/spec/unit/platform/query_helpers_spec.rb b/spec/unit/platform/query_helpers_spec.rb
new file mode 100644
index 0000000000..2414bdf552
--- /dev/null
+++ b/spec/unit/platform/query_helpers_spec.rb
@@ -0,0 +1,32 @@
+#
+# Author:: Bryan McLellan <btm@loftninjas.org>
+# Copyright:: Copyright (c) 2014 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 "Chef::Platform#windows_server_2003?" do
+ it "returns false early when not on windows" do
+ Chef::Platform.stub(:windows?).and_return(false)
+ expect(Chef::Platform).not_to receive(:require)
+ expect(Chef::Platform.windows_server_2003?).to be_false
+ end
+
+ # CHEF-4888: Need to call WIN32OLE.ole_initialize in new threads
+ it "does not raise an exception" do
+ expect { Thread.fork { Chef::Platform.windows_server_2003? }.join }.not_to raise_error
+ end
+end
diff --git a/spec/unit/platform_spec.rb b/spec/unit/platform_spec.rb
index e0386a1a61..3d7aef98a0 100644
--- a/spec/unit/platform_spec.rb
+++ b/spec/unit/platform_spec.rb
@@ -37,7 +37,8 @@ describe "Chef::Platform supports" do
:mswin,
:mingw32,
:windows,
- :gcel
+ :gcel,
+ :ibm_powerkvm
].each do |platform|
it "#{platform}" do
Chef::Platform.platforms.should have_key(platform)
@@ -47,209 +48,226 @@ end
describe Chef::Platform do
- before :all do
- @original_platform_map = Chef::Platform.platforms
- end
+ context "while testing with fake data" do
- after :all do ||
- Chef::Platform.platforms = @original_platform_map
- end
+ before :all do
+ @original_platform_map = Chef::Platform.platforms
+ end
- before(:each) do
- Chef::Platform.platforms = {
- :darwin => {
- ">= 10.11" => {
- :file => "new_darwinian"
+ after :all do ||
+ Chef::Platform.platforms = @original_platform_map
+ end
+
+ before(:each) do
+ Chef::Platform.platforms = {
+ :darwin => {
+ ">= 10.11" => {
+ :file => "new_darwinian"
+ },
+ "9.2.2" => {
+ :file => "darwinian",
+ :else => "thing"
+ },
+ :default => {
+ :file => "old school",
+ :snicker => "snack"
+ }
},
- "9.2.2" => {
- :file => "darwinian",
- :else => "thing"
+ :mars_volta => {
},
:default => {
- :file => "old school",
- :snicker => "snack"
+ :file => Chef::Provider::File,
+ :pax => "brittania",
+ :cat => "nice"
}
- },
- :mars_volta => {
- },
- :default => {
- :file => Chef::Provider::File,
- :pax => "brittania",
- :cat => "nice"
}
- }
- @events = Chef::EventDispatch::Dispatcher.new
- end
+ @events = Chef::EventDispatch::Dispatcher.new
+ end
- it "should allow you to look up a platform by name and version, returning the provider map for it" do
- pmap = Chef::Platform.find("Darwin", "9.2.2")
- pmap.should be_a_kind_of(Hash)
- pmap[:file].should eql("darwinian")
- end
+ it "should allow you to look up a platform by name and version, returning the provider map for it" do
+ pmap = Chef::Platform.find("Darwin", "9.2.2")
+ pmap.should be_a_kind_of(Hash)
+ pmap[:file].should eql("darwinian")
+ end
- it "should allow you to look up a platform by name and version using \"greater than\" style operators" do
- pmap = Chef::Platform.find("Darwin", "11.1.0")
- pmap.should be_a_kind_of(Hash)
- pmap[:file].should eql("new_darwinian")
- end
+ it "should allow you to look up a platform by name and version using \"greater than\" style operators" do
+ pmap = Chef::Platform.find("Darwin", "11.1.0")
+ pmap.should be_a_kind_of(Hash)
+ pmap[:file].should eql("new_darwinian")
+ end
- it "should use the default providers for an os if the specific version does not exist" do
- pmap = Chef::Platform.find("Darwin", "1")
- pmap.should be_a_kind_of(Hash)
- pmap[:file].should eql("old school")
- end
+ it "should use the default providers for an os if the specific version does not exist" do
+ pmap = Chef::Platform.find("Darwin", "1")
+ pmap.should be_a_kind_of(Hash)
+ pmap[:file].should eql("old school")
+ end
- it "should use the default providers if the os doesn't give me a default, but does exist" do
- pmap = Chef::Platform.find("mars_volta", "1")
- pmap.should be_a_kind_of(Hash)
- pmap[:file].should eql(Chef::Provider::File)
- end
+ it "should use the default providers if the os doesn't give me a default, but does exist" do
+ pmap = Chef::Platform.find("mars_volta", "1")
+ pmap.should be_a_kind_of(Hash)
+ pmap[:file].should eql(Chef::Provider::File)
+ end
- it "should use the default provider if the os does not exist" do
- pmap = Chef::Platform.find("AIX", "1")
- pmap.should be_a_kind_of(Hash)
- pmap[:file].should eql(Chef::Provider::File)
- end
+ it "should use the default provider if the os does not exist" do
+ pmap = Chef::Platform.find("AIX", "1")
+ pmap.should be_a_kind_of(Hash)
+ pmap[:file].should eql(Chef::Provider::File)
+ end
- it "should merge the defaults for an os with the specific version" do
- pmap = Chef::Platform.find("Darwin", "9.2.2")
- pmap[:file].should eql("darwinian")
- pmap[:snicker].should eql("snack")
- end
+ it "should merge the defaults for an os with the specific version" do
+ pmap = Chef::Platform.find("Darwin", "9.2.2")
+ pmap[:file].should eql("darwinian")
+ pmap[:snicker].should eql("snack")
+ end
- it "should merge the defaults for an os with the universal defaults" do
- pmap = Chef::Platform.find("Darwin", "9.2.2")
- pmap[:file].should eql("darwinian")
- pmap[:pax].should eql("brittania")
- end
+ it "should merge the defaults for an os with the universal defaults" do
+ pmap = Chef::Platform.find("Darwin", "9.2.2")
+ pmap[:file].should eql("darwinian")
+ pmap[:pax].should eql("brittania")
+ end
- it "should allow you to look up a provider for a platform directly by symbol" do
- Chef::Platform.find_provider("Darwin", "9.2.2", :file).should eql("darwinian")
- end
+ it "should allow you to look up a provider for a platform directly by symbol" do
+ Chef::Platform.find_provider("Darwin", "9.2.2", :file).should eql("darwinian")
+ end
- it "should raise an exception if a provider cannot be found for a resource type" do
- lambda { Chef::Platform.find_provider("Darwin", "9.2.2", :coffee) }.should raise_error(ArgumentError)
- end
+ it "should raise an exception if a provider cannot be found for a resource type" do
+ lambda { Chef::Platform.find_provider("Darwin", "9.2.2", :coffee) }.should raise_error(ArgumentError)
+ end
- it "should look up a provider for a resource with a Chef::Resource object" do
- kitty = Chef::Resource::Cat.new("loulou")
- Chef::Platform.find_provider("Darwin", "9.2.2", kitty).should eql("nice")
- end
+ it "should look up a provider for a resource with a Chef::Resource object" do
+ kitty = Chef::Resource::Cat.new("loulou")
+ Chef::Platform.find_provider("Darwin", "9.2.2", kitty).should eql("nice")
+ end
- it "should look up a provider with a node and a Chef::Resource object" do
- kitty = Chef::Resource::Cat.new("loulou")
- node = Chef::Node.new
- node.name("Intel")
- node.automatic_attrs[:platform] = "mac_os_x"
- node.automatic_attrs[:platform_version] = "9.2.2"
- Chef::Platform.find_provider_for_node(node, kitty).should eql("nice")
- end
+ it "should look up a provider with a node and a Chef::Resource object" do
+ kitty = Chef::Resource::Cat.new("loulou")
+ node = Chef::Node.new
+ node.name("Intel")
+ node.automatic_attrs[:platform] = "mac_os_x"
+ node.automatic_attrs[:platform_version] = "9.2.2"
+ Chef::Platform.find_provider_for_node(node, kitty).should eql("nice")
+ end
- it "should not throw an exception when the platform version has an unknown format" do
- Chef::Platform.find_provider(:darwin, "bad-version", :file).should eql("old school")
- end
+ it "should not throw an exception when the platform version has an unknown format" do
+ Chef::Platform.find_provider(:darwin, "bad-version", :file).should eql("old school")
+ end
- it "should prefer an explicit provider" do
- kitty = Chef::Resource::Cat.new("loulou")
- kitty.stub(:provider).and_return(Chef::Provider::File)
- node = Chef::Node.new
- node.name("Intel")
- node.automatic_attrs[:platform] = "mac_os_x"
- node.automatic_attrs[:platform_version] = "9.2.2"
- Chef::Platform.find_provider_for_node(node, kitty).should eql(Chef::Provider::File)
- end
+ it "should prefer an explicit provider" do
+ kitty = Chef::Resource::Cat.new("loulou")
+ kitty.stub(:provider).and_return(Chef::Provider::File)
+ node = Chef::Node.new
+ node.name("Intel")
+ node.automatic_attrs[:platform] = "mac_os_x"
+ node.automatic_attrs[:platform_version] = "9.2.2"
+ Chef::Platform.find_provider_for_node(node, kitty).should eql(Chef::Provider::File)
+ end
- it "should look up a provider based on the resource name if nothing else matches" do
- kitty = Chef::Resource::Cat.new("loulou")
- class Chef::Provider::Cat < Chef::Provider; end
- Chef::Platform.platforms[:default].delete(:cat)
- node = Chef::Node.new
- node.name("Intel")
- node.automatic_attrs[:platform] = "mac_os_x"
- node.automatic_attrs[:platform_version] = "8.5"
- Chef::Platform.find_provider_for_node(node, kitty).should eql(Chef::Provider::Cat)
- end
+ it "should look up a provider based on the resource name if nothing else matches" do
+ kitty = Chef::Resource::Cat.new("loulou")
+ class Chef::Provider::Cat < Chef::Provider; end
+ Chef::Platform.platforms[:default].delete(:cat)
+ node = Chef::Node.new
+ node.name("Intel")
+ node.automatic_attrs[:platform] = "mac_os_x"
+ node.automatic_attrs[:platform_version] = "8.5"
+ Chef::Platform.find_provider_for_node(node, kitty).should eql(Chef::Provider::Cat)
+ end
- def setup_file_resource
- node = Chef::Node.new
- node.automatic_attrs[:platform] = "mac_os_x"
- node.automatic_attrs[:platform_version] = "9.2.2"
- run_context = Chef::RunContext.new(node, {}, @events)
- [ Chef::Resource::File.new("whateva", run_context), run_context ]
- end
+ def setup_file_resource
+ node = Chef::Node.new
+ node.automatic_attrs[:platform] = "mac_os_x"
+ node.automatic_attrs[:platform_version] = "9.2.2"
+ run_context = Chef::RunContext.new(node, {}, @events)
+ [ Chef::Resource::File.new("whateva", run_context), run_context ]
+ end
- it "returns a provider object given a Chef::Resource object which has a valid run context and an action" do
- file, run_context = setup_file_resource
- provider = Chef::Platform.provider_for_resource(file, :foo)
- provider.should be_an_instance_of(Chef::Provider::File)
- provider.new_resource.should equal(file)
- provider.run_context.should equal(run_context)
- end
+ it "returns a provider object given a Chef::Resource object which has a valid run context and an action" do
+ file, run_context = setup_file_resource
+ provider = Chef::Platform.provider_for_resource(file, :foo)
+ provider.should be_an_instance_of(Chef::Provider::File)
+ provider.new_resource.should equal(file)
+ provider.run_context.should equal(run_context)
+ end
- it "returns a provider object given a Chef::Resource object which has a valid run context without an action" do
- file, run_context = setup_file_resource
- provider = Chef::Platform.provider_for_resource(file)
- provider.should be_an_instance_of(Chef::Provider::File)
- provider.new_resource.should equal(file)
- provider.run_context.should equal(run_context)
- end
+ it "returns a provider object given a Chef::Resource object which has a valid run context without an action" do
+ file, run_context = setup_file_resource
+ provider = Chef::Platform.provider_for_resource(file)
+ provider.should be_an_instance_of(Chef::Provider::File)
+ provider.new_resource.should equal(file)
+ provider.run_context.should equal(run_context)
+ end
- it "raises an error when trying to find the provider for a resource with no run context" do
- file = Chef::Resource::File.new("whateva")
- lambda {Chef::Platform.provider_for_resource(file)}.should raise_error(ArgumentError)
- end
+ it "raises an error when trying to find the provider for a resource with no run context" do
+ file = Chef::Resource::File.new("whateva")
+ lambda {Chef::Platform.provider_for_resource(file)}.should raise_error(ArgumentError)
+ end
- it "does not support finding a provider by resource and node -- a run context is required" do
- lambda {Chef::Platform.provider_for_node('node', 'resource')}.should raise_error(NotImplementedError)
- end
+ it "does not support finding a provider by resource and node -- a run context is required" do
+ lambda {Chef::Platform.provider_for_node('node', 'resource')}.should raise_error(NotImplementedError)
+ end
+
+ it "should update the provider map with map" do
+ Chef::Platform.set(
+ :platform => :darwin,
+ :version => "9.2.2",
+ :resource => :file,
+ :provider => "masterful"
+ )
+ Chef::Platform.platforms[:darwin]["9.2.2"][:file].should eql("masterful")
+ Chef::Platform.set(
+ :platform => :darwin,
+ :resource => :file,
+ :provider => "masterful"
+ )
+ Chef::Platform.platforms[:darwin][:default][:file].should eql("masterful")
+ Chef::Platform.set(
+ :resource => :file,
+ :provider => "masterful"
+ )
+ Chef::Platform.platforms[:default][:file].should eql("masterful")
+
+ Chef::Platform.set(
+ :platform => :hero,
+ :version => "9.2.2",
+ :resource => :file,
+ :provider => "masterful"
+ )
+ Chef::Platform.platforms[:hero]["9.2.2"][:file].should eql("masterful")
+
+ Chef::Platform.set(
+ :resource => :file,
+ :provider => "masterful"
+ )
+ Chef::Platform.platforms[:default][:file].should eql("masterful")
+
+ Chef::Platform.platforms = {}
+
+ Chef::Platform.set(
+ :resource => :file,
+ :provider => "masterful"
+ )
+ Chef::Platform.platforms[:default][:file].should eql("masterful")
+
+ Chef::Platform.platforms = { :neurosis => {} }
+ Chef::Platform.set(:platform => :neurosis, :resource => :package, :provider => "masterful")
+ Chef::Platform.platforms[:neurosis][:default][:package].should eql("masterful")
- it "should update the provider map with map" do
- Chef::Platform.set(
- :platform => :darwin,
- :version => "9.2.2",
- :resource => :file,
- :provider => "masterful"
- )
- Chef::Platform.platforms[:darwin]["9.2.2"][:file].should eql("masterful")
- Chef::Platform.set(
- :platform => :darwin,
- :resource => :file,
- :provider => "masterful"
- )
- Chef::Platform.platforms[:darwin][:default][:file].should eql("masterful")
- Chef::Platform.set(
- :resource => :file,
- :provider => "masterful"
- )
- Chef::Platform.platforms[:default][:file].should eql("masterful")
-
- Chef::Platform.set(
- :platform => :hero,
- :version => "9.2.2",
- :resource => :file,
- :provider => "masterful"
- )
- Chef::Platform.platforms[:hero]["9.2.2"][:file].should eql("masterful")
-
- Chef::Platform.set(
- :resource => :file,
- :provider => "masterful"
- )
- Chef::Platform.platforms[:default][:file].should eql("masterful")
-
- Chef::Platform.platforms = {}
-
- Chef::Platform.set(
- :resource => :file,
- :provider => "masterful"
- )
- Chef::Platform.platforms[:default][:file].should eql("masterful")
-
- Chef::Platform.platforms = { :neurosis => {} }
- Chef::Platform.set(:platform => :neurosis, :resource => :package, :provider => "masterful")
- Chef::Platform.platforms[:neurosis][:default][:package].should eql("masterful")
+ end
end
+ context "while testing the configured platform data" do
+
+ it "should use the solaris package provider on Solaris <11" do
+ pmap = Chef::Platform.find("Solaris2", "5.9")
+ pmap[:package].should eql(Chef::Provider::Package::Solaris)
+ end
+
+ it "should use the IPS package provider on Solaris 11" do
+ pmap = Chef::Platform.find("Solaris2", "5.11")
+ pmap[:package].should eql(Chef::Provider::Package::Ips)
+ end
+
+ end
end
diff --git a/spec/unit/policy_builder/expand_node_object_spec.rb b/spec/unit/policy_builder/expand_node_object_spec.rb
index 5c6f39d28c..a1e0b881d5 100644
--- a/spec/unit/policy_builder/expand_node_object_spec.rb
+++ b/spec/unit/policy_builder/expand_node_object_spec.rb
@@ -244,7 +244,7 @@ describe Chef::PolicyBuilder::ExpandNodeObject do
it "sets the override run_list on the node" do
expect(node.run_list).to eq([override_runlist])
- expect(policy_builder.original_runlist).to eq(primary_runlist)
+ expect(node.primary_runlist).to eq(primary_runlist)
end
it "reports that a temporary policy is being used" do
diff --git a/spec/unit/provider/cron_spec.rb b/spec/unit/provider/cron_spec.rb
index 3a7a96c549..b78266fb25 100644
--- a/spec/unit/provider/cron_spec.rb
+++ b/spec/unit/provider/cron_spec.rb
@@ -19,15 +19,137 @@
require 'spec_helper'
describe Chef::Provider::Cron do
+ describe "when with special time string" do
+ before do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @new_resource = Chef::Resource::Cron.new("cronhole some stuff", @run_context)
+ @new_resource.user "root"
+ @new_resource.minute "30"
+ @new_resource.command "/bin/true"
+ @new_resource.time :reboot
+ @provider = Chef::Provider::Cron.new(@new_resource, @run_context)
+ end
+
+ context "with a matching entry in the user's crontab" do
+ before :each do
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+@reboot /bin/true param1 param2
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+CRONTAB
+ end
+
+ it "should set cron_exists" do
+ @provider.load_current_resource
+ @provider.cron_exists.should == true
+ @provider.cron_empty.should == false
+ end
+
+ it "should pull the details out of the cron line" do
+ cron = @provider.load_current_resource
+ cron.time.should == :reboot
+ cron.command.should == '/bin/true param1 param2'
+ end
+
+ it "should pull env vars out" do
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+MAILTO=foo@example.com
+SHELL=/bin/foosh
+PATH=/bin:/foo
+HOME=/home/foo
+@reboot /bin/true param1 param2
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+CRONTAB
+ cron = @provider.load_current_resource
+ cron.mailto.should == 'foo@example.com'
+ cron.shell.should == '/bin/foosh'
+ cron.path.should == '/bin:/foo'
+ cron.home.should == '/home/foo'
+ cron.time.should == :reboot
+ cron.command.should == '/bin/true param1 param2'
+ end
+
+ it "should parse and load generic and standard environment variables from cron entry" do
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+# Chef Name: cronhole some stuff
+MAILTO=warn@example.com
+TEST=lol
+FLAG=1
+@reboot /bin/true
+CRONTAB
+ cron = @provider.load_current_resource
+
+ cron.mailto.should == "warn@example.com"
+ cron.environment.should == {"TEST" => "lol", "FLAG" => "1"}
+ end
+
+ it "should not break with variables that match the cron resource internals" do
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+# Chef Name: cronhole some stuff
+MINUTE=40
+REBOOT=midnight
+TEST=lol
+ENVIRONMENT=production
+@reboot /bin/true
+CRONTAB
+ cron = @provider.load_current_resource
+
+ cron.time.should == :reboot
+ cron.environment.should == {"MINUTE" => "40", "REBOOT" => "midnight", "TEST" => "lol", "ENVIRONMENT" => "production"}
+ end
+
+ it "should report the match" do
+ Chef::Log.should_receive(:debug).with("Found cron '#{@new_resource.name}'")
+ @provider.load_current_resource
+ end
+
+ describe "action_create" do
+ before :each do
+ @provider.stub!(:write_crontab)
+ @provider.stub!(:read_crontab).and_return(nil)
+ end
+
+ context "when there is no existing crontab" do
+ before :each do
+ @provider.cron_exists = false
+ @provider.cron_empty = true
+ end
+
+ it "should create a crontab with the entry" do
+ @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+# Chef Name: cronhole some stuff
+@reboot /bin/true
+ ENDCRON
+ @provider.run_action(:create)
+ end
+ end
+ end
+ end
+ end
+
before do
@node = Chef::Node.new
@events = Chef::EventDispatch::Dispatcher.new
@run_context = Chef::RunContext.new(@node, {}, @events)
+
@new_resource = Chef::Resource::Cron.new("cronhole some stuff", @run_context)
@new_resource.user "root"
@new_resource.minute "30"
@new_resource.command "/bin/true"
-
@provider = Chef::Provider::Cron.new(@new_resource, @run_context)
end
@@ -110,6 +232,7 @@ CRONTAB
cron.day.should == '*'
cron.month.should == '1'
cron.weekday.should == '*'
+ cron.time.should == nil
cron.command.should == '/bin/true param1 param2'
end
@@ -138,6 +261,7 @@ CRONTAB
cron.day.should == '*'
cron.month.should == '1'
cron.weekday.should == '*'
+ cron.time.should == nil
cron.command.should == '/bin/true param1 param2'
end
@@ -227,6 +351,7 @@ CRONTAB
cron.day.should == '*'
cron.month.should == '*'
cron.weekday.should == '*'
+ cron.time.should == nil
cron.command.should == nil
end
@@ -244,6 +369,7 @@ CRONTAB
cron.day.should == '*'
cron.month.should == '*'
cron.weekday.should == '*'
+ cron.time.should == nil
cron.command.should == nil
end
@@ -265,6 +391,7 @@ CRONTAB
cron.day.should == '*'
cron.month.should == '*'
cron.weekday.should == '*'
+ cron.time.should == nil
cron.command.should == nil
end
end
@@ -286,6 +413,11 @@ CRONTAB
end
end
+ it "should return true if special time string doesn't match" do
+ @new_resource.send(:time, :reboot)
+ @provider.cron_different?.should eql(true)
+ end
+
it "should return true if environment doesn't match" do
@new_resource.environment "FOO" => "something_else"
@provider.cron_different?.should eql(true)
@@ -833,4 +965,46 @@ MAILTO=foo@example.com
end
end
+
+ describe "weekday_in_crontab" do
+ context "when weekday is symbol" do
+ it "should return weekday in crontab format" do
+ @new_resource.weekday :wednesday
+ @provider.send(:weekday_in_crontab).should eq("3")
+ end
+
+ it "should raise an error with an unknown weekday" do
+ expect { @new_resource.weekday :caturday }.to raise_error(RangeError)
+ end
+ end
+
+ context "when weekday is a number in a string" do
+ it "should return the string" do
+ @new_resource.weekday "3"
+ @provider.send(:weekday_in_crontab).should eq("3")
+ end
+
+ it "should raise an error with an out of range number" do
+ expect { @new_resource.weekday "-1" }.to raise_error(RangeError)
+ end
+ end
+
+ context "when weekday is string with the name of the week" do
+ it "should return the string" do
+ @new_resource.weekday "mon"
+ @provider.send(:weekday_in_crontab).should eq("mon")
+ end
+ end
+
+ context "when weekday is an integer" do
+ it "should return the integer" do
+ @new_resource.weekday 1
+ @provider.send(:weekday_in_crontab).should eq("1")
+ end
+
+ it "should raise an error with an out of range integer" do
+ expect { @new_resource.weekday 45 }.to raise_error(RangeError)
+ end
+ end
+ end
end
diff --git a/spec/unit/provider/group_spec.rb b/spec/unit/provider/group_spec.rb
index 9ff9f85c7c..b138f6b210 100644
--- a/spec/unit/provider/group_spec.rb
+++ b/spec/unit/provider/group_spec.rb
@@ -96,6 +96,11 @@ describe Chef::Provider::User do
@provider.compare_group.should be_false
end
+ it "should coerce an integer to a string for comparison" do
+ @current_resource.stub!(:gid).and_return("500")
+ @provider.compare_group.should be_false
+ end
+
it "should return false if append is true and the group member(s) already exists" do
@current_resource.members << "extra_user"
@new_resource.stub(:append).and_return(true)
diff --git a/spec/unit/provider/ifconfig/debian_spec.rb b/spec/unit/provider/ifconfig/debian_spec.rb
index c2e2d1bfd1..c6a37fdd5b 100644
--- a/spec/unit/provider/ifconfig/debian_spec.rb
+++ b/spec/unit/provider/ifconfig/debian_spec.rb
@@ -53,38 +53,264 @@ describe Chef::Provider::Ifconfig::Debian do
let(:config_filename_ifcfg) { "/etc/network/interfaces.d/ifcfg-#{new_resource.device}" }
- describe "generate_config for action_add" do
+ describe "generate_config" do
- let(:config_file_ifaces) { StringIO.new }
+ context "when writing a file" do
+ let(:config_file_ifcfg) { StringIO.new }
- let(:config_file_ifcfg) { StringIO.new }
+ let(:tempfile) { Tempfile.new("rspec-chef-ifconfig-debian") }
- before do
- expect(FileUtils).to receive(:cp)
- expect(File).to receive(:open).with(config_filename_ifaces).and_return(StringIO.new)
- expect(File).to receive(:open).with(config_filename_ifaces, "w").and_yield(config_file_ifaces)
- expect(File).to receive(:new).with(config_filename_ifcfg, "w").and_return(config_file_ifcfg)
- expect(File).to receive(:exist?).with(config_filename_ifaces).and_return(true)
- end
+ let(:tempdir_path) { Dir.mktmpdir("rspec-chef-ifconfig-debian-dir") }
+
+ let(:config_filename_ifcfg) { "#{tempdir_path}/ifcfg-#{new_resource.device}" }
+
+ before do
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_FILE", tempfile.path)
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_DOT_D_DIR", tempdir_path)
+ expect(File).to receive(:new).with(config_filename_ifcfg, "w").and_return(config_file_ifcfg)
+ end
+
+ it "should write a network-script" do
+ provider.run_action(:add)
+ expect(config_file_ifcfg.string).to match(/^iface eth0 inet static\s*$/)
+ expect(config_file_ifcfg.string).to match(/^\s+address 10\.0\.0\.1\s*$/)
+ expect(config_file_ifcfg.string).to match(/^\s+netmask 255\.255\.254\.0\s*$/)
+ end
+
+ context "when the interface_dot_d directory does not exist" do
+ before do
+ FileUtils.rmdir tempdir_path
+ expect(File.exists?(tempdir_path)).to be_false
+ end
+
+ it "should create the /etc/network/interfaces.d directory" do
+ provider.run_action(:add)
+ expect(File.exists?(tempdir_path)).to be_true
+ expect(File.directory?(tempdir_path)).to be_true
+ end
- it "should create network-scripts directory" do
- expect(File).to receive(:directory?).with(File.dirname(config_filename_ifcfg)).and_return(false)
- expect(Dir).to receive(:mkdir).with(File.dirname(config_filename_ifcfg))
- provider.run_action(:add)
+ it "should mark the resource as updated" do
+ provider.run_action(:add)
+ expect(new_resource.updated_by_last_action?).to be_true
+ end
+ end
+
+ context "when the interface_dot_d directory exists" do
+ before do
+ expect(File.exists?(tempdir_path)).to be_true
+ end
+
+ it "should still mark the resource as updated (we still write a file to it)" do
+ provider.run_action(:add)
+ expect(new_resource.updated_by_last_action?).to be_true
+ end
+ end
end
- it "should write configure network-scripts directory" do
- expect(File).to receive(:directory?).with(File.dirname(config_filename_ifcfg)).and_return(true)
- provider.run_action(:add)
- expect(config_file_ifaces.string).to match(/^\s*source\s+\/etc\/network\/interfaces[.]d\/[*]\s*$/)
+ context "when the file is up-to-date" do
+ let(:tempfile) { Tempfile.new("rspec-chef-ifconfig-debian") }
+
+ let(:tempdir_path) { Dir.mktmpdir("rspec-chef-ifconfig-debian-dir") }
+
+ let(:config_filename_ifcfg) { "#{tempdir_path}/ifcfg-#{new_resource.device}" }
+
+ before do
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_FILE", tempfile.path)
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_DOT_D_DIR", tempdir_path)
+ config_file_ifcfg = StringIO.new(<<-EOF
+iface eth0 inet static
+ address 10.0.0.1
+ netmask 255.255.254.0
+EOF
+ )
+ expect(File).to receive(:new).with(config_filename_ifcfg, "w").and_return(config_file_ifcfg)
+ expect(File.exists?(tempdir_path)).to be_true # since the file exists, the enclosing dir must also exist
+ end
+
+ context "when the /etc/network/interfaces file has the source line" do
+ let(:expected_string) do
+ <<-EOF
+a line
+source #{tempdir_path}/*
+another line
+EOF
+ end
+
+ before do
+ tempfile.write(expected_string)
+ tempfile.close
+ end
+
+ it "should preserve all the contents" do
+ provider.run_action(:add)
+ expect(IO.read(tempfile.path)).to eq(expected_string)
+ end
+
+ it "should not mark the resource as updated" do
+ provider.run_action(:add)
+ pending "superclass ifconfig provider is not idempotent"
+ expect(new_resource.updated_by_last_action?).to be_false
+ end
+ end
+
+ context "when the /etc/network/interfaces file does not have the source line" do
+ let(:expected_string) do
+ <<-EOF
+a line
+another line
+source #{tempdir_path}/*
+EOF
+ end
+
+ before do
+ tempfile.write("a line\nanother line\n")
+ tempfile.close
+ end
+
+ it "should preserve the original contents and add the source line" do
+ provider.run_action(:add)
+ expect(IO.read(tempfile.path)).to eq(expected_string)
+ end
+
+ it "should mark the resource as updated" do
+ provider.run_action(:add)
+ expect(new_resource.updated_by_last_action?).to be_true
+ end
+ end
end
- it "should write a network-script" do
- expect(File).to receive(:directory?).with(File.dirname(config_filename_ifcfg)).and_return(true)
- provider.run_action(:add)
- expect(config_file_ifcfg.string).to match(/^iface eth0 inet static\s*$/)
- expect(config_file_ifcfg.string).to match(/^\s+address 10\.0\.0\.1\s*$/)
- expect(config_file_ifcfg.string).to match(/^\s+netmask 255\.255\.254\.0\s*$/)
+ describe "when running under why run" do
+
+ before do
+ Chef::Config[:why_run] = true
+ end
+
+ after do
+ Chef::Config[:why_run] = false
+ end
+
+ context "when writing a file" do
+ let(:config_file_ifcfg) { StringIO.new }
+
+ let(:tempfile) { Tempfile.new("rspec-chef-ifconfig-debian") }
+
+ let(:tempdir_path) { Dir.mktmpdir("rspec-chef-ifconfig-debian-dir") }
+
+ let(:config_filename_ifcfg) { "#{tempdir_path}/ifcfg-#{new_resource.device}" }
+
+ before do
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_FILE", tempfile.path)
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_DOT_D_DIR", tempdir_path)
+ expect(File).not_to receive(:new).with(config_filename_ifcfg, "w")
+ end
+
+ it "should write a network-script" do
+ provider.run_action(:add)
+ expect(config_file_ifcfg.string).not_to match(/^iface eth0 inet static\s*$/)
+ expect(config_file_ifcfg.string).not_to match(/^\s+address 10\.0\.0\.1\s*$/)
+ expect(config_file_ifcfg.string).not_to match(/^\s+netmask 255\.255\.254\.0\s*$/)
+ end
+
+ context "when the interface_dot_d directory does not exist" do
+ before do
+ FileUtils.rmdir tempdir_path
+ expect(File.exists?(tempdir_path)).to be_false
+ end
+
+ it "should not create the /etc/network/interfaces.d directory" do
+ provider.run_action(:add)
+ expect(File.exists?(tempdir_path)).not_to be_true
+ end
+
+ it "should mark the resource as updated" do
+ provider.run_action(:add)
+ expect(new_resource.updated_by_last_action?).to be_true
+ end
+ end
+
+ context "when the interface_dot_d directory exists" do
+ before do
+ expect(File.exists?(tempdir_path)).to be_true
+ end
+
+ it "should still mark the resource as updated (we still write a file to it)" do
+ provider.run_action(:add)
+ expect(new_resource.updated_by_last_action?).to be_true
+ end
+ end
+ end
+
+ context "when the file is up-to-date" do
+ let(:tempfile) { Tempfile.new("rspec-chef-ifconfig-debian") }
+
+ let(:tempdir_path) { Dir.mktmpdir("rspec-chef-ifconfig-debian-dir") }
+
+ let(:config_filename_ifcfg) { "#{tempdir_path}/ifcfg-#{new_resource.device}" }
+
+ before do
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_FILE", tempfile.path)
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_DOT_D_DIR", tempdir_path)
+ config_file_ifcfg = StringIO.new(<<-EOF
+iface eth0 inet static
+ address 10.0.0.1
+ netmask 255.255.254.0
+ EOF
+ )
+ expect(File).not_to receive(:new).with(config_filename_ifcfg, "w")
+ expect(File.exists?(tempdir_path)).to be_true # since the file exists, the enclosing dir must also exist
+ end
+
+ context "when the /etc/network/interfaces file has the source line" do
+ let(:expected_string) do
+ <<-EOF
+a line
+source #{tempdir_path}/*
+another line
+ EOF
+ end
+
+ before do
+ tempfile.write(expected_string)
+ tempfile.close
+ end
+
+ it "should preserve all the contents" do
+ provider.run_action(:add)
+ expect(IO.read(tempfile.path)).to eq(expected_string)
+ end
+
+ it "should not mark the resource as updated" do
+ provider.run_action(:add)
+ pending "superclass ifconfig provider is not idempotent"
+ expect(new_resource.updated_by_last_action?).to be_false
+ end
+ end
+
+ context "when the /etc/network/interfaces file does not have the source line" do
+ let(:expected_string) do
+ <<-EOF
+a line
+another line
+source #{tempdir_path}/*
+ EOF
+ end
+
+ before do
+ tempfile.write("a line\nanother line\n")
+ tempfile.close
+ end
+
+ it "should preserve the original contents and not add the source line" do
+ provider.run_action(:add)
+ expect(IO.read(tempfile.path)).to eq("a line\nanother line\n")
+ end
+
+ it "should mark the resource as updated" do
+ provider.run_action(:add)
+ expect(new_resource.updated_by_last_action?).to be_true
+ end
+ end
+ end
end
end
@@ -98,4 +324,5 @@ describe Chef::Provider::Ifconfig::Debian do
provider.run_action(:delete)
end
end
+
end
diff --git a/spec/unit/provider/mount/mount_spec.rb b/spec/unit/provider/mount/mount_spec.rb
index 99e78590f1..e27cf71e01 100644
--- a/spec/unit/provider/mount/mount_spec.rb
+++ b/spec/unit/provider/mount/mount_spec.rb
@@ -141,6 +141,17 @@ describe Chef::Provider::Mount::Mount do
@provider.current_resource.mounted.should be_true
end
+ it "should set mounted true if the symlink target of the device is relative and is found in the mounts list - CHEF-4957" do
+ target = "xsdz1"
+
+ ::File.stub(:symlink?).with("#{@new_resource.device}").and_return(true)
+ ::File.stub(:readlink).with("#{@new_resource.device}").and_return(target)
+
+ @provider.stub(:shell_out!).and_return(OpenStruct.new(:stdout => "/dev/xsdz1 on /tmp/foo type ext3 (rw)\n"))
+ @provider.load_current_resource()
+ @provider.current_resource.mounted.should be_true
+ end
+
it "should set mounted true if the mount point is found last in the mounts list" do
mount = "/dev/sdy1 on #{@new_resource.mount_point} type ext3 (rw)\n"
mount << "#{@new_resource.device} on #{@new_resource.mount_point} type ext3 (rw)\n"
@@ -199,6 +210,20 @@ describe Chef::Provider::Mount::Mount do
@provider.current_resource.enabled.should be_true
end
+ it "should set enabled to true if the symlink target is relative and is in fstab - CHEF-4957" do
+ target = "xsdz1"
+
+ ::File.stub(:symlink?).with("#{@new_resource.device}").and_return(true)
+ ::File.stub(:readlink).with("#{@new_resource.device}").and_return(target)
+
+ fstab = "/dev/sdz1 /tmp/foo ext3 defaults 1 2\n"
+
+ ::File.stub(:foreach).with("/etc/fstab").and_yield fstab
+
+ @provider.load_current_resource
+ @provider.current_resource.enabled.should be_true
+ end
+
it "should set enabled to false if the mount point is not in fstab" do
fstab = "/dev/sdy1 #{@new_resource.mount_point} ext3 defaults 1 2\n"
::File.stub(:foreach).with("/etc/fstab").and_yield fstab
diff --git a/spec/unit/provider/ohai_spec.rb b/spec/unit/provider/ohai_spec.rb
index 8b8a6b5939..2085f44309 100644
--- a/spec/unit/provider/ohai_spec.rb
+++ b/spec/unit/provider/ohai_spec.rb
@@ -41,9 +41,8 @@ describe Chef::Provider::Ohai do
:newdata => "somevalue"
}
}
- mock_ohai.stub(:all_plugins).and_return(true)
- mock_ohai.stub(:require_plugin).and_return(true)
- mock_ohai.stub(:data).and_return(mock_ohai[:data],
+ mock_ohai.stub!(:all_plugins).and_return(true)
+ mock_ohai.stub!(:data).and_return(mock_ohai[:data],
mock_ohai[:data2])
Ohai::System.stub(:new).and_return(mock_ohai)
Chef::Platform.stub(:find_platform_and_version).and_return({ "platform" => @platform,
diff --git a/spec/unit/provider/package/dpkg_spec.rb b/spec/unit/provider/package/dpkg_spec.rb
index 6ba7695a1e..22edeb7b9b 100644
--- a/spec/unit/provider/package/dpkg_spec.rb
+++ b/spec/unit/provider/package/dpkg_spec.rb
@@ -72,6 +72,10 @@ describe Chef::Provider::Package::Dpkg do
it 'if distro-specific version provided' do
check_version('1.11.4-1ubuntu1~lucid')
end
+
+ it 'returns the version if an epoch is used' do
+ check_version('1:1.8.3-2')
+ end
end
it "gets the source package name from dpkg-deb correctly when the package name has `-', `+' or `.' characters" do
diff --git a/spec/unit/provider/package/windows/msi_spec.rb b/spec/unit/provider/package/windows/msi_spec.rb
new file mode 100644
index 0000000000..c8a63ad066
--- /dev/null
+++ b/spec/unit/provider/package/windows/msi_spec.rb
@@ -0,0 +1,60 @@
+#
+# Author:: Bryan McLellan <btm@loftninjas.org>
+# Copyright:: Copyright (c) 2014 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 Chef::Provider::Package::Windows::MSI, :windows_only do
+ let(:node) { double('Chef::Node') }
+ let(:events) { double('Chef::Events').as_null_object } # mock all the methods
+ let(:run_context) { double('Chef::RunContext', :node => node, :events => events) }
+ let(:new_resource) { Chef::Resource::WindowsPackage.new("calculator.msi") }
+ let(:provider) { Chef::Provider::Package::Windows::MSI.new(new_resource) }
+
+ describe "expand_options" do
+ it "returns an empty string if passed no options" do
+ expect(provider.expand_options(nil)).to eql ""
+ end
+
+ it "returns a string with a leading space if passed options" do
+ expect(provider.expand_options("--train nope --town no_way")).to eql(" --train nope --town no_way")
+ end
+ end
+
+ describe "installed_version" do
+ it "returns the installed version" do
+ provider.stub(:get_product_property).and_return("{23170F69-40C1-2702-0920-000001000000}")
+ provider.stub(:get_installed_version).with("{23170F69-40C1-2702-0920-000001000000}").and_return("3.14159.1337.42")
+ expect(provider.installed_version).to eql("3.14159.1337.42")
+ end
+ end
+
+ describe "package_version" do
+ it "returns the version of a package" do
+ provider.stub(:get_product_property).with(/calculator.msi$/, "ProductVersion").and_return(42)
+ expect(provider.package_version).to eql(42)
+ end
+ end
+
+ describe "install_package" do
+ # calls shell_out!
+ end
+
+ describe "remove_package" do
+ # calls shell_out!
+ end
+end
diff --git a/spec/unit/provider/package/windows_spec.rb b/spec/unit/provider/package/windows_spec.rb
new file mode 100644
index 0000000000..e94404eea5
--- /dev/null
+++ b/spec/unit/provider/package/windows_spec.rb
@@ -0,0 +1,80 @@
+#
+# Author:: Bryan McLellan <btm@loftninjas.org>
+# Copyright:: Copyright (c) 2014 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 Chef::Provider::Package::Windows, :windows_only do
+ let(:node) { double('Chef::Node') }
+ let(:events) { double('Chef::Events').as_null_object } # mock all the methods
+ let(:run_context) { double('Chef::RunContext', :node => node, :events => events) }
+ let(:new_resource) { Chef::Resource::WindowsPackage.new("calculator.msi") }
+ let(:provider) { Chef::Provider::Package::Windows.new(new_resource, run_context) }
+
+ describe "load_current_resource" do
+ before(:each) do
+ provider.stub(:package_provider).and_return(double('package_provider',
+ :installed_version => "1.0", :package_version => "2.0"))
+ end
+
+ it "creates a current resource with the name of the new resource" do
+ provider.load_current_resource
+ expect(provider.current_resource).to be_a(Chef::Resource::WindowsPackage)
+ expect(provider.current_resource.name).to eql("calculator.msi")
+ end
+
+ it "sets the current version if the package is installed" do
+ provider.load_current_resource
+ expect(provider.current_resource.version).to eql("1.0")
+ end
+
+ it "sets the version to be installed" do
+ provider.load_current_resource
+ expect(provider.new_resource.version).to eql("2.0")
+ end
+ end
+
+ describe "package_provider" do
+ it "sets the package provider to MSI if the the installer type is :msi" do
+ provider.stub(:installer_type).and_return(:msi)
+ expect(provider.package_provider).to be_a(Chef::Provider::Package::Windows::MSI)
+ end
+
+ it "raises an error if the installer_type is unknown" do
+ provider.stub(:installer_type).and_return(:apt_for_windows)
+ expect { provider.package_provider }.to raise_error
+ end
+ end
+
+ describe "installer_type" do
+ it "it returns @installer_type if it is set" do
+ provider.new_resource.installer_type("downeaster")
+ expect(provider.installer_type).to eql("downeaster")
+ end
+
+ it "sets installer_type to msi if the source ends in .msi" do
+ provider.new_resource.source("microsoft_installer.msi")
+ expect(provider.installer_type).to eql(:msi)
+ end
+
+ it "raises an error if it cannot determine the installer type" do
+ provider.new_resource.installer_type(nil)
+ provider.new_resource.source("tomfoolery.now")
+ expect { provider.installer_type }.to raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/unit/provider/service/macosx_spec.rb b/spec/unit/provider/service/macosx_spec.rb
index 65639f2084..1e9656aeac 100644
--- a/spec/unit/provider/service/macosx_spec.rb
+++ b/spec/unit/provider/service/macosx_spec.rb
@@ -46,14 +46,32 @@ describe Chef::Provider::Service::Macosx do
let(:events) {Chef::EventDispatch::Dispatcher.new}
let(:run_context) { Chef::RunContext.new(node, {}, events) }
let(:provider) { described_class.new(new_resource, run_context) }
- let(:stdout) { StringIO.new }
+ let(:launchctl_stdout) { StringIO.new }
+ let(:plutil_stdout) { String.new <<-XML }
+<?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>Label</key>
+ <string>io.redis.redis-server</string>
+</dict>
+</plist>
+XML
["redis-server", "io.redis.redis-server"].each do |service_name|
before do
Dir.stub(:glob).and_return(["/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist"], [])
provider.stub(:shell_out!).
with("launchctl list", {:group => 1001, :user => 101}).
- and_return(double("ouput", :stdout => stdout))
+ and_return(double("Status", :stdout => launchctl_stdout))
+ provider.stub(:shell_out).
+ with(/launchctl list /,
+ {:group => nil, :user => nil}).
+ and_return(double("Status",
+ :stdout => launchctl_stdout, :exitstatus => 0))
+ provider.stub(:shell_out!).
+ with(/plutil -convert xml1 -o/).
+ and_return(double("Status", :stdout => plutil_stdout))
File.stub(:stat).and_return(double("stat", :gid => 1001, :uid => 101))
end
@@ -64,7 +82,7 @@ describe Chef::Provider::Service::Macosx do
describe "#load_current_resource" do
context "when launchctl returns pid in service list" do
- let(:stdout) { StringIO.new <<-SVC_LIST }
+ let(:launchctl_stdout) { StringIO.new <<-SVC_LIST }
12761 - 0x100114220.old.machinit.thing
7777 - io.redis.redis-server
- - com.lol.stopped-thing
@@ -84,21 +102,21 @@ describe Chef::Provider::Service::Macosx do
end
describe "running unsupported actions" do
+ let(:launchctl_stdout) { StringIO.new <<-SVC_LIST }
+12761 - 0x100114220.old.machinit.thing
+7777 - io.redis.redis-server
+- - com.lol.stopped-thing
+SVC_LIST
+
before do
Dir.stub(:glob).and_return(["/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist"], [])
end
- it "should throw an exception when enable action is attempted" do
- lambda {provider.run_action(:enable)}.should raise_error(Chef::Exceptions::UnsupportedAction)
- end
it "should throw an exception when reload action is attempted" do
lambda {provider.run_action(:reload)}.should raise_error(Chef::Exceptions::UnsupportedAction)
end
- it "should throw an exception when disable action is attempted" do
- lambda {provider.run_action(:disable)}.should raise_error(Chef::Exceptions::UnsupportedAction)
- end
end
context "when launchctl returns empty service pid" do
- let(:stdout) { StringIO.new <<-SVC_LIST }
+ let(:launchctl_stdout) { StringIO.new <<-SVC_LIST }
12761 - 0x100114220.old.machinit.thing
- - io.redis.redis-server
- - com.lol.stopped-thing
@@ -118,7 +136,7 @@ describe Chef::Provider::Service::Macosx do
end
context "when launchctl doesn't return service entry at all" do
- let(:stdout) { StringIO.new <<-SVC_LIST }
+ let(:launchctl_stdout) { StringIO.new <<-SVC_LIST }
12761 - 0x100114220.old.machinit.thing
- - com.lol.stopped-thing
SVC_LIST
diff --git a/spec/unit/provider/service/solaris_smf_service_spec.rb b/spec/unit/provider/service/solaris_smf_service_spec.rb
index 887c1f6b5f..af1351a4ff 100644
--- a/spec/unit/provider/service/solaris_smf_service_spec.rb
+++ b/spec/unit/provider/service/solaris_smf_service_spec.rb
@@ -54,34 +54,47 @@ describe Chef::Provider::Service::Solaris do
describe "when discovering the current service state" do
it "should create a current resource with the name of the new resource" do
- @provider.stub(:popen4).with("/bin/svcs -l chef").and_return(@status)
+ @provider.stub!(:shell_out!).with("/bin/svcs -l chef").and_return(@status)
Chef::Resource::Service.should_receive(:new).and_return(@current_resource)
@provider.load_current_resource
end
it "should return the current resource" do
- @provider.stub(:popen4).with("/bin/svcs -l chef").and_return(@status)
+ @provider.stub!(:shell_out!).with("/bin/svcs -l chef").and_return(@status)
@provider.load_current_resource.should eql(@current_resource)
end
- it "should popen4 '/bin/svcs -l service_name'" do
- @provider.should_receive(:popen4).with("/bin/svcs -l chef").and_return(@status)
+ it "should call '/bin/svcs -l service_name'" do
+ @provider.should_receive(:shell_out!).with("/bin/svcs -l chef").and_return(@status)
@provider.load_current_resource
end
it "should mark service as not running" do
- @provider.stub(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @provider.stub!(:shell_out!).and_return(@status)
@current_resource.should_receive(:running).with(false)
@provider.load_current_resource
end
it "should mark service as running" do
- @stdout.stub(:each).and_yield("state online")
- @provider.stub(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @status = mock("Status", :exitstatus => 0, :stdout => 'state online')
+ @provider.stub!(:shell_out!).and_return(@status)
@current_resource.should_receive(:running).with(true)
@provider.load_current_resource
end
+
+ it "should not mark service as maintenance" do
+ @provider.stub!(:shell_out!).and_return(@status)
+ @provider.load_current_resource
+ @provider.maintenance.should be_false
+ end
+
+ it "should mark service as maintenance" do
+ @status = mock("Status", :exitstatus => 0, :stdout => 'state maintenance')
+ @provider.stub!(:shell_out!).and_return(@status)
+ @provider.load_current_resource
+ @provider.maintenance.should be_true
+ end
end
describe "when enabling the service" do
@@ -91,19 +104,31 @@ describe Chef::Provider::Service::Solaris do
end
it "should call svcadm enable -s chef" do
- @new_resource.stub(:enable_command).and_return("#{@new_resource.enable_command}")
+ @new_resource.stub!(:enable_command).and_return("#{@new_resource.enable_command}")
+ @provider.should_not_receive(:shell_out!).with("/usr/sbin/svcadm clear #{@current_resource.service_name}")
@provider.should_receive(:shell_out!).with("/usr/sbin/svcadm enable -s #{@current_resource.service_name}").and_return(@status)
- @provider.enable_service.should be_true
+ @provider.enable_service.should be_true
@current_resource.enabled.should be_true
end
it "should call svcadm enable -s chef for start_service" do
- @new_resource.stub(:start_command).and_return("#{@new_resource.start_command}")
+ @new_resource.stub!(:start_command).and_return("#{@new_resource.start_command}")
+ @provider.should_not_receive(:shell_out!).with("/usr/sbin/svcadm clear #{@current_resource.service_name}")
@provider.should_receive(:shell_out!).with("/usr/sbin/svcadm enable -s #{@current_resource.service_name}").and_return(@status)
@provider.start_service.should be_true
@current_resource.enabled.should be_true
end
+ it "should call svcadm clear chef for start_service when state maintenance" do
+ @status = mock("Status", :exitstatus => 0, :stdout => 'state maintenance')
+ @provider.stub!(:shell_out!).and_return(@status)
+ @provider.load_current_resource
+ @new_resource.stub!(:enable_command).and_return("#{@new_resource.enable_command}")
+ @provider.should_receive(:shell_out!).with("/usr/sbin/svcadm clear #{@current_resource.service_name}").and_return(@status)
+ @provider.should_receive(:shell_out!).with("/usr/sbin/svcadm enable -s #{@current_resource.service_name}").and_return(@status)
+ @provider.enable_service.should be_true
+ @current_resource.enabled.should be_true
+ end
end
diff --git a/spec/unit/recipe_spec.rb b/spec/unit/recipe_spec.rb
index b0cd04b245..2bdf470143 100644
--- a/spec/unit/recipe_spec.rb
+++ b/spec/unit/recipe_spec.rb
@@ -339,6 +339,7 @@ describe Chef::Recipe do
describe "include_recipe" do
it "should evaluate another recipe with include_recipe" do
node.should_receive(:loaded_recipe).with(:openldap, "gigantor")
+ run_context.stub(:unreachable_cookbook?).with(:openldap).and_return(false)
run_context.include_recipe "openldap::gigantor"
res = run_context.resource_collection.resources(:cat => "blanket")
res.name.should eql("blanket")
@@ -347,6 +348,7 @@ describe Chef::Recipe do
it "should load the default recipe for a cookbook if include_recipe is called without a ::" do
node.should_receive(:loaded_recipe).with(:openldap, "default")
+ run_context.stub(:unreachable_cookbook?).with(:openldap).and_return(false)
run_context.include_recipe "openldap"
res = run_context.resource_collection.resources(:cat => "blanket")
res.name.should eql("blanket")
@@ -355,12 +357,14 @@ describe Chef::Recipe do
it "should store that it has seen a recipe in the run_context" do
node.should_receive(:loaded_recipe).with(:openldap, "default")
+ run_context.stub(:unreachable_cookbook?).with(:openldap).and_return(false)
run_context.include_recipe "openldap"
run_context.loaded_recipe?("openldap").should be_true
end
it "should not include the same recipe twice" do
node.should_receive(:loaded_recipe).with(:openldap, "default").exactly(:once)
+ run_context.stub(:unreachable_cookbook?).with(:openldap).and_return(false)
cookbook_collection[:openldap].should_receive(:load_recipe).with("default", run_context)
recipe.include_recipe "openldap"
cookbook_collection[:openldap].should_not_receive(:load_recipe).with("default", run_context)
diff --git a/spec/unit/resource/conditional_spec.rb b/spec/unit/resource/conditional_spec.rb
index 1be7bcea71..4df185bcd6 100644
--- a/spec/unit/resource/conditional_spec.rb
+++ b/spec/unit/resource/conditional_spec.rb
@@ -24,12 +24,13 @@ describe Chef::Resource::Conditional do
Mixlib::ShellOut.any_instance.stub(:run_command).and_return(nil)
@status = OpenStruct.new(:success? => true)
Mixlib::ShellOut.any_instance.stub(:status).and_return(@status)
+ @parent_resource = Chef::Resource.new(nil, Chef::Node.new)
end
describe "when created as an `only_if`" do
describe "after running a successful command" do
before do
- @conditional = Chef::Resource::Conditional.only_if("true")
+ @conditional = Chef::Resource::Conditional.only_if(@parent_resource, "true")
end
it "indicates that resource convergence should continue" do
@@ -40,7 +41,7 @@ describe Chef::Resource::Conditional do
describe "after running a negative/false command" do
before do
@status.send("success?=", false)
- @conditional = Chef::Resource::Conditional.only_if("false")
+ @conditional = Chef::Resource::Conditional.only_if(@parent_resource, "false")
end
it "indicates that resource convergence should not continue" do
@@ -50,8 +51,8 @@ describe Chef::Resource::Conditional do
describe 'after running a command which timed out' do
before do
- @conditional = Chef::Resource::Conditional.only_if("false")
- @conditional.stub(:shell_out).and_raise(Chef::Exceptions::CommandTimeout)
+ @conditional = Chef::Resource::Conditional.only_if(@parent_resource, "false")
+ Chef::GuardInterpreter::DefaultGuardInterpreter.any_instance.stub(:shell_out).and_raise(Chef::Exceptions::CommandTimeout)
end
it 'indicates that resource convergence should not continue' do
@@ -66,7 +67,7 @@ describe Chef::Resource::Conditional do
describe "after running a block that returns a truthy value" do
before do
- @conditional = Chef::Resource::Conditional.only_if { Object.new }
+ @conditional = Chef::Resource::Conditional.only_if(@parent_resource) { Object.new }
end
it "indicates that resource convergence should continue" do
@@ -76,7 +77,7 @@ describe Chef::Resource::Conditional do
describe "after running a block that returns a falsey value" do
before do
- @conditional = Chef::Resource::Conditional.only_if { nil }
+ @conditional = Chef::Resource::Conditional.only_if(@parent_resource) { nil }
end
it "indicates that resource convergence should not continue" do
@@ -88,7 +89,7 @@ describe Chef::Resource::Conditional do
describe "when created as a `not_if`" do
describe "after running a successful/true command" do
before do
- @conditional = Chef::Resource::Conditional.not_if("true")
+ @conditional = Chef::Resource::Conditional.not_if(@parent_resource, "true")
end
it "indicates that resource convergence should not continue" do
@@ -99,7 +100,7 @@ describe Chef::Resource::Conditional do
describe "after running a failed/false command" do
before do
@status.send("success?=", false)
- @conditional = Chef::Resource::Conditional.not_if("false")
+ @conditional = Chef::Resource::Conditional.not_if(@parent_resource, "false")
end
it "indicates that resource convergence should continue" do
@@ -109,8 +110,8 @@ describe Chef::Resource::Conditional do
describe 'after running a command which timed out' do
before do
- @conditional = Chef::Resource::Conditional.not_if("false")
- @conditional.stub(:shell_out).and_raise(Chef::Exceptions::CommandTimeout)
+ @conditional = Chef::Resource::Conditional.not_if(@parent_resource, "false")
+ Chef::GuardInterpreter::DefaultGuardInterpreter.any_instance.stub(:shell_out).and_raise(Chef::Exceptions::CommandTimeout)
end
it 'indicates that resource convergence should continue' do
@@ -125,7 +126,7 @@ describe Chef::Resource::Conditional do
describe "after running a block that returns a truthy value" do
before do
- @conditional = Chef::Resource::Conditional.not_if { Object.new }
+ @conditional = Chef::Resource::Conditional.not_if(@parent_resource) { Object.new }
end
it "indicates that resource convergence should not continue" do
@@ -135,7 +136,7 @@ describe Chef::Resource::Conditional do
describe "after running a block that returns a falsey value" do
before do
- @conditional = Chef::Resource::Conditional.not_if { nil }
+ @conditional = Chef::Resource::Conditional.not_if(@parent_resource) { nil }
end
it "indicates that resource convergence should continue" do
diff --git a/spec/unit/resource/cron_spec.rb b/spec/unit/resource/cron_spec.rb
index 355a7f09ba..cf821e3d32 100644
--- a/spec/unit/resource/cron_spec.rb
+++ b/spec/unit/resource/cron_spec.rb
@@ -143,8 +143,13 @@ describe Chef::Resource::Cron do
lambda { @resource.month "13" }.should raise_error(RangeError)
end
- it "should reject any weekday over 7" do
- lambda { @resource.weekday "8" }.should raise_error(RangeError)
+ describe "weekday" do
+ it "should reject any weekday over 7" do
+ lambda { @resource.weekday "8" }.should raise_error(RangeError)
+ end
+ it "should reject any symbols which don't represent day of week" do
+ lambda { @resource.weekday :foo }.should raise_error(RangeError)
+ end
end
it "should convert integer schedule values to a string" do
diff --git a/spec/unit/resource/powershell_spec.rb b/spec/unit/resource/powershell_spec.rb
index a35e37c696..da20c4f0bf 100644
--- a/spec/unit/resource/powershell_spec.rb
+++ b/spec/unit/resource/powershell_spec.rb
@@ -36,7 +36,91 @@ describe Chef::Resource::PowershellScript do
@resource.should be_a_kind_of(Chef::Resource::PowershellScript)
end
- context "windowsscript" do
+ it "should set convert_boolean_return to false by default" do
+ @resource.convert_boolean_return.should == false
+ end
+
+ it "should return the value for convert_boolean_return that was set" do
+ @resource.convert_boolean_return true
+ @resource.convert_boolean_return.should == true
+ @resource.convert_boolean_return false
+ @resource.convert_boolean_return.should == false
+ end
+
+ context "when using guards" do
+ let(:resource) { @resource }
+ before(:each) do
+ resource.stub(:run_action)
+ resource.stub(:updated).and_return(true)
+ end
+
+ it "inherits exactly the :cwd, :environment, :group, :path, :user, :umask, and :architecture attributes from a parent resource class" do
+ inherited_difference = Chef::Resource::PowershellScript.guard_inherited_attributes -
+ [:cwd, :environment, :group, :path, :user, :umask, :architecture ]
+
+ inherited_difference.should == []
+ end
+
+ it "should allow guard interpreter to be set to Chef::Resource::Script" do
+ resource.guard_interpreter(:script)
+ allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(false)
+ resource.only_if("echo hi")
+ end
+
+ it "should allow guard interpreter to be set to Chef::Resource::Bash derived from Chef::Resource::Script" do
+ resource.guard_interpreter(:bash)
+ allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(false)
+ resource.only_if("echo hi")
+ end
+
+ it "should allow guard interpreter to be set to Chef::Resource::PowershellScript derived indirectly from Chef::Resource::Script" do
+ resource.guard_interpreter(:powershell_script)
+ allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(false)
+ resource.only_if("echo hi")
+ end
+
+ it "should enable convert_boolean_return by default for guards in the context of powershell_script when no guard params are specified" do
+ allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(true)
+ allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with(
+ {:convert_boolean_return => true, :code => "$true"}).and_return(Proc.new {})
+ resource.only_if("$true")
+ end
+
+ it "should enable convert_boolean_return by default for guards in non-Chef::Resource::Script derived resources when no guard params are specified" do
+ node = Chef::Node.new
+ run_context = Chef::RunContext.new(node, nil, nil)
+ file_resource = Chef::Resource::File.new('idontexist', run_context)
+ file_resource.guard_interpreter :powershell_script
+
+ allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with(
+ {:convert_boolean_return => true, :code => "$true"}).and_return(Proc.new {})
+ resource.only_if("$true")
+ end
+
+ it "should enable convert_boolean_return by default for guards in the context of powershell_script when guard params are specified" do
+ guard_parameters = {:cwd => '/etc/chef', :architecture => :x86_64}
+ allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with(
+ {:convert_boolean_return => true, :code => "$true"}.merge(guard_parameters)).and_return(Proc.new {})
+ resource.only_if("$true", guard_parameters)
+ end
+
+ it "should pass convert_boolean_return as true if it was specified as true in a guard parameter" do
+ guard_parameters = {:cwd => '/etc/chef', :convert_boolean_return => true, :architecture => :x86_64}
+ allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with(
+ {:convert_boolean_return => true, :code => "$true"}.merge(guard_parameters)).and_return(Proc.new {})
+ resource.only_if("$true", guard_parameters)
+ end
+
+ it "should pass convert_boolean_return as false if it was specified as true in a guard parameter" do
+ other_guard_parameters = {:cwd => '/etc/chef', :architecture => :x86_64}
+ parameters_with_boolean_disabled = other_guard_parameters.merge({:convert_boolean_return => false, :code => "$true"})
+ allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with(
+ parameters_with_boolean_disabled).and_return(Proc.new {})
+ resource.only_if("$true", parameters_with_boolean_disabled)
+ end
+ end
+
+ context "as a script running in Windows-based scripting language" do
let(:resource_instance) { @resource }
let(:resource_instance_name ) { @resource.command }
let(:resource_name) { :powershell_script }
@@ -44,5 +128,4 @@ describe Chef::Resource::PowershellScript do
it_should_behave_like "a Windows script resource"
end
-
end
diff --git a/spec/unit/resource/subversion_spec.rb b/spec/unit/resource/subversion_spec.rb
index 67593c5a7c..ae06ce665a 100644
--- a/spec/unit/resource/subversion_spec.rb
+++ b/spec/unit/resource/subversion_spec.rb
@@ -55,4 +55,9 @@ describe Chef::Resource::Subversion do
@svn.svn_arguments.should be_nil
end
+ it "hides password from custom exception message" do
+ @svn.svn_password "l33th4x0rpa$$w0rd"
+ e = @svn.customize_exception(Chef::Exceptions::Exec.new "Exception with password #{@svn.svn_password}")
+ e.message.include?(@svn.svn_password).should be_false
+ end
end
diff --git a/spec/unit/resource/windows_package_spec.rb b/spec/unit/resource/windows_package_spec.rb
new file mode 100644
index 0000000000..c9ef8d910c
--- /dev/null
+++ b/spec/unit/resource/windows_package_spec.rb
@@ -0,0 +1,74 @@
+#
+# Author:: Bryan McLellan <btm@loftninjas.org>
+# Copyright:: Copyright (c) 2014 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 Chef::Resource::WindowsPackage, "initialize", :windows_only do
+
+ let(:resource) { Chef::Resource::WindowsPackage.new("solitaire.msi") }
+
+ it "returns a Chef::Resource::WindowsPackage" do
+ expect(resource).to be_a_kind_of(Chef::Resource::WindowsPackage)
+ end
+
+ it "sets the resource_name to :windows_package" do
+ expect(resource.resource_name).to eql(:windows_package)
+ end
+
+ it "sets the provider to Chef::Provider::Package::Windows" do
+ expect(resource.provider).to eql(Chef::Provider::Package::Windows)
+ end
+
+ it "supports setting installer_type" do
+ resource.installer_type("msi")
+ expect(resource.installer_type).to eql("msi")
+ end
+
+ # String, Integer
+ [ "600", 600 ].each do |val|
+ it "supports setting a timeout as a #{val.class}" do
+ resource.timeout(val)
+ expect(resource.timeout).to eql(val)
+ end
+ end
+
+ # String, Integer, Array
+ [ "42", 42, [47, 48, 49] ].each do |val|
+ it "supports setting an alternate return value as a #{val.class}" do
+ resource.returns(val)
+ expect(resource.returns).to eql(val)
+ end
+ end
+
+ it "coverts a source to an absolute path" do
+ ::File.stub(:absolute_path).and_return("c:\\Files\\frost.msi")
+ resource.source("frost.msi")
+ expect(resource.source).to eql "c:\\Files\\frost.msi"
+ end
+
+ it "converts slashes to backslashes in the source path" do
+ ::File.stub(:absolute_path).and_return("c:\\frost.msi")
+ resource.source("c:/frost.msi")
+ expect(resource.source).to eql "c:\\frost.msi"
+ end
+
+ it "defaults source to the resource name" do
+ # it's a little late to stub out File.absolute_path
+ expect(resource.source).to include("solitaire.msi")
+ end
+end
diff --git a/spec/unit/resource_reporter_spec.rb b/spec/unit/resource_reporter_spec.rb
index 52fd44e692..d412234596 100644
--- a/spec/unit/resource_reporter_spec.rb
+++ b/spec/unit/resource_reporter_spec.rb
@@ -38,7 +38,6 @@ describe Chef::ResourceReporter do
@rest_client = double("Chef::REST (mock)")
@rest_client.stub(:post_rest).and_return(true)
@resource_reporter = Chef::ResourceReporter.new(@rest_client)
- @run_id = @resource_reporter.run_id
@new_resource = Chef::Resource::File.new("/tmp/a-file.txt")
@new_resource.cookbook_name = "monkey"
@cookbook_version = double("Cookbook::Version", :version => "1.2.3")
@@ -49,6 +48,7 @@ describe Chef::ResourceReporter do
@events = Chef::EventDispatch::Dispatcher.new
@run_context = Chef::RunContext.new(@node, {}, @events)
@run_status = Chef::RunStatus.new(@node, @events)
+ @run_id = @run_status.run_id
Time.stub(:now).and_return(@start_time, @end_time)
end
diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb
index e9a60c9861..60f3bdb8ea 100644
--- a/spec/unit/resource_spec.rb
+++ b/spec/unit/resource_spec.rb
@@ -344,7 +344,7 @@ describe Chef::Resource do
expected_keys = [ :allowed_actions, :params, :provider, :updated,
:updated_by_last_action, :before, :supports,
:noop, :ignore_failure, :name, :source_line,
- :action, :retries, :retry_delay, :elapsed_time]
+ :action, :retries, :retry_delay, :elapsed_time, :guard_interpreter]
(hash.keys - expected_keys).should == []
(expected_keys - hash.keys).should == []
hash[:name].should eql("funk")
@@ -526,6 +526,28 @@ describe Chef::Resource do
snitch_var2.should be_false
end
+ describe "guard_interpreter attribute" do
+ let(:resource) { @resource }
+
+ it "should be set to :default by default" do
+ resource.guard_interpreter.should == :default
+ end
+
+ it "if set to :default should return :default when read" do
+ resource.guard_interpreter(:default)
+ resource.guard_interpreter.should == :default
+ end
+
+ it "should raise Chef::Exceptions::ValidationFailed on an attempt to set the guard_interpreter attribute to something other than a Symbol" do
+ expect { resource.guard_interpreter('command_dot_com') }.to raise_error(Chef::Exceptions::ValidationFailed)
+ end
+
+ it "should not raise an exception when setting the guard interpreter attribute to a Symbol" do
+ Chef::GuardInterpreter::ResourceGuardInterpreter.stub(:new).and_return(nil)
+ expect { resource.guard_interpreter(:command_dot_com) }.not_to raise_error
+ end
+ end
+
end
describe "should_skip?" do
diff --git a/spec/unit/rest_spec.rb b/spec/unit/rest_spec.rb
index a53b4c9507..a8eb1ac7db 100644
--- a/spec/unit/rest_spec.rb
+++ b/spec/unit/rest_spec.rb
@@ -59,17 +59,38 @@ describe Chef::REST do
let(:log_stringio) { StringIO.new }
+ let(:request_id) {"1234"}
+
let(:rest) do
Chef::REST::CookieJar.stub(:instance).and_return({})
+ Chef::RequestID.instance.stub(:request_id).and_return(request_id)
rest = Chef::REST.new(base_url, nil, nil)
Chef::REST::CookieJar.instance.clear
rest
end
+ let(:standard_read_headers) {{"Accept"=>"application/json", "Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID"=>request_id}}
+ let(:standard_write_headers) {{"Accept"=>"application/json", "Content-Type"=>"application/json", "Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID"=>request_id}}
+
before(:each) do
Chef::Log.init(log_stringio)
end
+ it "should have content length validation middleware after compressor middleware" do
+ middlewares = rest.instance_variable_get(:@middlewares)
+ content_length = middlewares.find_index { |e| e.is_a? Chef::HTTP::ValidateContentLength }
+ decompressor = middlewares.find_index { |e| e.is_a? Chef::HTTP::Decompressor }
+
+ content_length.should_not be_nil
+ decompressor.should_not be_nil
+ (decompressor < content_length).should be_true
+ end
+
+ it "should allow the options hash to be frozen" do
+ options = {}.freeze
+ # should not raise any exception
+ Chef::REST.new(base_url, nil, nil, options)
+ end
describe "calling an HTTP verb on a path or absolute URL" do
it "adds a relative URL to the base url it was initialized with" do
@@ -82,7 +103,7 @@ describe Chef::REST do
it "makes a :GET request with the composed url object" do
rest.should_receive(:send_http_request).
- with(:GET, monkey_uri, STANDARD_READ_HEADERS, false).
+ with(:GET, monkey_uri, standard_read_headers, false).
and_return([1,2,3])
rest.should_receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3])
rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
@@ -94,12 +115,9 @@ describe Chef::REST do
rest.get_rest("monkey", true)
end
- STANDARD_READ_HEADERS = {"Accept"=>"application/json", "Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3"}
- STANDARD_WRITE_HEADERS = {"Accept"=>"application/json", "Content-Type"=>"application/json", "Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3"}
-
it "makes a :DELETE request with the composed url object" do
rest.should_receive(:send_http_request).
- with(:DELETE, monkey_uri, STANDARD_READ_HEADERS, false).
+ with(:DELETE, monkey_uri, standard_read_headers, false).
and_return([1,2,3])
rest.should_receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3])
rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
@@ -108,7 +126,7 @@ describe Chef::REST do
it "makes a :POST request with the composed url object and data" do
rest.should_receive(:send_http_request).
- with(:POST, monkey_uri, STANDARD_WRITE_HEADERS, "\"data\"").
+ with(:POST, monkey_uri, standard_write_headers, "\"data\"").
and_return([1,2,3])
rest.should_receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3])
rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
@@ -117,7 +135,7 @@ describe Chef::REST do
it "makes a :PUT request with the composed url object and data" do
rest.should_receive(:send_http_request).
- with(:PUT, monkey_uri, STANDARD_WRITE_HEADERS, "\"data\"").
+ with(:PUT, monkey_uri, standard_write_headers, "\"data\"").
and_return([1,2,3])
rest.should_receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3])
rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
@@ -142,27 +160,27 @@ describe Chef::REST do
it 'calls the authn middleware' do
data = "\"secure data\""
- auth_headers = STANDARD_WRITE_HEADERS.merge({"auth_done"=>"yep"})
+ auth_headers = standard_write_headers.merge({"auth_done"=>"yep"})
rest.authenticator.should_receive(:handle_request).
- with(:POST, monkey_uri, STANDARD_WRITE_HEADERS, data).
+ with(:POST, monkey_uri, standard_write_headers, data).
and_return([:POST, monkey_uri, auth_headers, data])
rest.should_receive(:send_http_request).
with(:POST, monkey_uri, auth_headers, data).
and_return([1,2,3])
rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
- rest.raw_http_request(:POST, monkey_uri, STANDARD_WRITE_HEADERS, data)
+ rest.raw_http_request(:POST, monkey_uri, standard_write_headers, data)
end
it 'sets correct authn headers' do
data = "\"secure data\""
- method, uri, auth_headers, d = rest.authenticator.handle_request(:POST, monkey_uri, STANDARD_WRITE_HEADERS, data)
+ method, uri, auth_headers, d = rest.authenticator.handle_request(:POST, monkey_uri, standard_write_headers, data)
rest.should_receive(:send_http_request).
with(:POST, monkey_uri, auth_headers, data).
and_return([1,2,3])
rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
- rest.raw_http_request(:POST, monkey_uri, STANDARD_WRITE_HEADERS, data)
+ rest.raw_http_request(:POST, monkey_uri, standard_write_headers, data)
end
end
@@ -244,6 +262,7 @@ describe Chef::REST do
let(:rest) do
Net::HTTP.stub(:new).and_return(http_client)
Chef::REST::CookieJar.stub(:instance).and_return({})
+ Chef::RequestID.instance.stub(:request_id).and_return(request_id)
rest = Chef::REST.new(base_url, nil, nil)
Chef::REST::CookieJar.instance.clear
rest
@@ -254,6 +273,7 @@ describe Chef::REST do
'Accept' => 'application/json',
'X-Chef-Version' => Chef::VERSION,
'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
+ 'X-REMOTE-REQUEST-ID' => request_id
}
end
@@ -275,6 +295,7 @@ describe Chef::REST do
'X-Chef-Version' => Chef::VERSION,
'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
'Host' => host_header,
+ 'X-REMOTE-REQUEST-ID' => request_id
}
end
@@ -287,6 +308,11 @@ describe Chef::REST do
rest.request(:GET, url, {})
end
+ it "should always include the X-Remote-Request-Id header" do
+ Net::HTTP::Get.should_receive(:new).with("/?foo=bar", base_headers).and_return(request_mock)
+ rest.request(:GET, url, {})
+ end
+
it "sets the user agent to chef-client" do
# XXX: must reset to default b/c knife changes the UA
Chef::REST::RESTRequest.user_agent = Chef::REST::RESTRequest::DEFAULT_UA
@@ -342,6 +368,7 @@ describe Chef::REST do
let(:rest) do
Net::HTTP.stub(:new).and_return(http_client)
Chef::REST::CookieJar.instance["#{url.host}:#{url.port}"] = "cookie monster"
+ Chef::RequestID.instance.stub(:request_id).and_return(request_id)
rest = Chef::REST.new(base_url, nil, nil)
rest
end
@@ -542,7 +569,20 @@ describe Chef::REST do
expected_headers = {'Accept' => "*/*",
'X-Chef-Version' => Chef::VERSION,
'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
- 'Host' => host_header}
+ 'Host' => host_header,
+ 'X-REMOTE-REQUEST-ID'=> request_id
+ }
+ Net::HTTP::Get.should_receive(:new).with("/?foo=bar", expected_headers).and_return(request_mock)
+ rest.streaming_request(url, {})
+ end
+
+ it "build a new HTTP GET request with the X-Remote-Request-Id header" do
+ expected_headers = {'Accept' => "*/*",
+ 'X-Chef-Version' => Chef::VERSION,
+ 'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
+ 'Host' => host_header,
+ 'X-REMOTE-REQUEST-ID'=> request_id
+ }
Net::HTTP::Get.should_receive(:new).with("/?foo=bar", expected_headers).and_return(request_mock)
rest.streaming_request(url, {})
end
diff --git a/spec/unit/run_context/cookbook_compiler_spec.rb b/spec/unit/run_context/cookbook_compiler_spec.rb
index 52f4772206..5c50c3dd4b 100644
--- a/spec/unit/run_context/cookbook_compiler_spec.rb
+++ b/spec/unit/run_context/cookbook_compiler_spec.rb
@@ -170,5 +170,17 @@ describe Chef::RunContext::CookbookCompiler do
:"circular-dep1",
:"test-with-circular-deps"]
end
+
+ it "determines if a cookbook is in the list of cookbooks reachable by dependency" do
+ node.run_list("test-with-deps::default", "test-with-deps::server")
+ compiler.cookbook_order.should == [:dependency1, :dependency2, :"test-with-deps"]
+ compiler.unreachable_cookbook?(:dependency1).should be_false
+ compiler.unreachable_cookbook?(:dependency2).should be_false
+ compiler.unreachable_cookbook?(:'test-with-deps').should be_false
+ compiler.unreachable_cookbook?(:'circular-dep1').should be_true
+ compiler.unreachable_cookbook?(:'circular-dep2').should be_true
+ end
+
+
end
end
diff --git a/spec/unit/run_context_spec.rb b/spec/unit/run_context_spec.rb
index 39b8a8a50d..813102527b 100644
--- a/spec/unit/run_context_spec.rb
+++ b/spec/unit/run_context_spec.rb
@@ -79,6 +79,13 @@ describe Chef::RunContext do
@node.include_attribute("test::george")
end
+ it "raises an error when attempting to include_recipe from a cookbook not reachable by run list or dependencies" do
+ @node.should_receive(:loaded_recipe).with(:ancient, "aliens")
+ lambda do
+ @run_context.include_recipe("ancient::aliens")
+ # In CHEF-5120, this becomes a Chef::Exceptions::MissingCookbookDependency error:
+ end.should raise_error(Chef::Exceptions::CookbookNotFound)
+ end
end
diff --git a/spec/unit/util/editor_spec.rb b/spec/unit/util/editor_spec.rb
new file mode 100644
index 0000000000..06370f7de0
--- /dev/null
+++ b/spec/unit/util/editor_spec.rb
@@ -0,0 +1,152 @@
+require 'spec_helper'
+require 'chef/util/editor'
+
+describe Chef::Util::Editor do
+ describe '#initialize' do
+ it 'takes an Enumerable of lines' do
+ editor = described_class.new(File.open(__FILE__))
+ expect(editor.lines).to be == IO.readlines(__FILE__)
+ end
+
+ it 'makes a copy of an Array' do
+ array = Array.new
+ editor = described_class.new(array)
+ expect(editor.lines).to_not be(array)
+ end
+ end
+
+ subject(:editor) { described_class.new(input_lines) }
+ let(:input_lines) { ['one', 'two', 'two', 'three'] }
+
+ describe '#append_line_after' do
+ context 'when there is no match' do
+ subject(:execute) { editor.append_line_after('missing', 'new') }
+
+ it('returns the number of added lines') { should be == 0 }
+ it 'does not add any lines' do
+ expect { execute }.to_not change { editor.lines }
+ end
+ end
+
+ context 'when there is a match' do
+ subject(:execute) { editor.append_line_after('two', 'new') }
+
+ it('returns the number of added lines') { should be == 2 }
+ it 'adds a line after each match' do
+ execute
+ expect(editor.lines).to be == ['one', 'two', 'new', 'two', 'new', 'three']
+ end
+ end
+
+ it 'matches a Regexp' do
+ expect(editor.append_line_after(/^ee/, 'new')).to be == 0
+ expect(editor.append_line_after(/ee$/, 'new')).to be == 1
+ end
+ end
+
+ describe '#append_line_if_missing' do
+ context 'when there is no match' do
+ subject(:execute) { editor.append_line_if_missing('missing', 'new') }
+
+ it('returns the number of added lines') { should be == 1 }
+ it 'adds a line to the end' do
+ execute
+ expect(editor.lines).to be == ['one', 'two', 'two', 'three', 'new']
+ end
+ end
+
+ context 'when there is a match' do
+ subject(:execute) { editor.append_line_if_missing('one', 'new') }
+
+ it('returns the number of added lines') { should be == 0 }
+ it 'does not add any lines' do
+ expect { execute }.to_not change { editor.lines }
+ end
+ end
+
+ it 'matches a Regexp' do
+ expect(editor.append_line_if_missing(/ee$/, 'new')).to be == 0
+ expect(editor.append_line_if_missing(/^ee/, 'new')).to be == 1
+ end
+ end
+
+ describe '#remove_lines' do
+ context 'when there is no match' do
+ subject(:execute) { editor.remove_lines('missing') }
+
+ it('returns the number of removed lines') { should be == 0 }
+ it 'does not remove any lines' do
+ expect { execute }.to_not change { editor.lines }
+ end
+ end
+
+ context 'when there is a match' do
+ subject(:execute) { editor.remove_lines('two') }
+
+ it('returns the number of removed lines') { should be == 2 }
+ it 'removes the matching lines' do
+ execute
+ expect(editor.lines).to be == ['one', 'three']
+ end
+ end
+
+ it 'matches a Regexp' do
+ expect(editor.remove_lines(/^ee/)).to be == 0
+ expect(editor.remove_lines(/ee$/)).to be == 1
+ end
+ end
+
+ describe '#replace' do
+ context 'when there is no match' do
+ subject(:execute) { editor.replace('missing', 'new') }
+
+ it('returns the number of changed lines') { should be == 0 }
+ it 'does not change any lines' do
+ expect { execute }.to_not change { editor.lines }
+ end
+ end
+
+ context 'when there is a match' do
+ subject(:execute) { editor.replace('two', 'new') }
+
+ it('returns the number of changed lines') { should be == 2 }
+ it 'replaces the matching portions' do
+ execute
+ expect(editor.lines).to be == ['one', 'new', 'new', 'three']
+ end
+ end
+
+ it 'matches a Regexp' do
+ expect(editor.replace(/^ee/, 'new')).to be == 0
+ expect(editor.replace(/ee$/, 'new')).to be == 1
+ expect(editor.lines).to be == ['one', 'two', 'two', 'thrnew']
+ end
+ end
+
+ describe '#replace_lines' do
+ context 'when there is no match' do
+ subject(:execute) { editor.replace_lines('missing', 'new') }
+
+ it('returns the number of changed lines') { should be == 0 }
+ it 'does not change any lines' do
+ expect { execute }.to_not change { editor.lines }
+ end
+ end
+
+ context 'when there is a match' do
+ subject(:execute) { editor.replace_lines('two', 'new') }
+
+ it('returns the number of replaced lines') { should be == 2 }
+ it 'replaces the matching line' do
+ execute
+ expect(editor.lines).to be == ['one', 'new', 'new', 'three']
+ end
+ end
+
+ it 'matches a Regexp' do
+ expect(editor.replace_lines(/^ee/, 'new')).to be == 0
+ expect(editor.replace_lines(/ee$/, 'new')).to be == 1
+ expect(editor.lines).to be == ['one', 'two', 'two', 'new']
+ end
+ end
+end
diff --git a/spec/unit/util/file_edit_spec.rb b/spec/unit/util/file_edit_spec.rb
index d1d87a6bda..139b29d9ce 100644
--- a/spec/unit/util/file_edit_spec.rb
+++ b/spec/unit/util/file_edit_spec.rb
@@ -17,6 +17,7 @@
#
require 'spec_helper'
+require 'tempfile'
describe Chef::Util::FileEdit do
@@ -80,6 +81,17 @@ new line inserted
EOF
end
+ let(:append_twice) do
+ <<-EOF
+127.0.0.1 localhost
+255.255.255.255 broadcasthost
+::1 localhost
+fe80::1%lo0 localhost
+once
+twice
+ EOF
+ end
+
let(:target_file) do
f = Tempfile.open('file_edit_spec')
f.write(starting_content)
@@ -104,7 +116,7 @@ new line inserted
# CHEF-5018: people have monkey patched this and it has accidentally been broken
it "should read the contents into memory as an array" do
- expect(fedit.send(:contents)).to be_instance_of(Array)
+ expect(fedit.send(:editor).lines).to be_instance_of(Array)
end
end
@@ -123,18 +135,21 @@ new line inserted
describe "search_file_replace" do
it "should accept regex passed in as a string (not Regexp object) and replace the match if there is one" do
fedit.search_file_replace("localhost", "replacement")
+ fedit.unwritten_changes?.should be_true
fedit.write_file
expect(edited_file_contents).to eq(localhost_replaced)
end
it "should accept regex passed in as a Regexp object and replace the match if there is one" do
fedit.search_file_replace(/localhost/, "replacement")
+ fedit.unwritten_changes?.should be_true
fedit.write_file
expect(edited_file_contents).to eq(localhost_replaced)
end
it "should do nothing if there isn't a match" do
fedit.search_file_replace(/pattern/, "replacement")
+ fedit.unwritten_changes?.should be_false
fedit.write_file
expect(edited_file_contents).to eq(starting_content)
end
@@ -143,6 +158,7 @@ new line inserted
describe "search_file_replace_line" do
it "should search for match and replace the whole line" do
fedit.search_file_replace_line(/localhost/, "replacement line")
+ fedit.unwritten_changes?.should be_true
fedit.write_file
expect(edited_file_contents).to eq(localhost_line_replaced)
end
@@ -151,6 +167,7 @@ new line inserted
describe "search_file_delete" do
it "should search for match and delete the match" do
fedit.search_file_delete(/localhost/)
+ fedit.unwritten_changes?.should be_true
fedit.write_file
expect(edited_file_contents).to eq(localhost_deleted)
end
@@ -159,6 +176,7 @@ new line inserted
describe "search_file_delete_line" do
it "should search for match and delete the matching line" do
fedit.search_file_delete_line(/localhost/)
+ fedit.unwritten_changes?.should be_true
fedit.write_file
expect(edited_file_contents).to eq(localhost_line_deleted)
end
@@ -167,6 +185,7 @@ new line inserted
describe "insert_line_after_match" do
it "should search for match and insert the given line after the matching line" do
fedit.insert_line_after_match(/localhost/, "new line inserted")
+ fedit.unwritten_changes?.should be_true
fedit.write_file
expect(edited_file_contents).to eq(append_after_all_localhost)
end
@@ -175,14 +194,31 @@ new line inserted
describe "insert_line_if_no_match" do
it "should search for match and insert the given line if no line match" do
fedit.insert_line_if_no_match(/pattern/, "new line inserted")
+ fedit.unwritten_changes?.should be_true
fedit.write_file
expect(edited_file_contents).to eq(append_after_content)
end
it "should do nothing if there is a match" do
fedit.insert_line_if_no_match(/localhost/, "replacement")
+ fedit.unwritten_changes?.should be_false
fedit.write_file
expect(edited_file_contents).to eq(starting_content)
end
+
+ it "should work more than once" do
+ fedit.insert_line_if_no_match(/missing/, "once")
+ fedit.insert_line_if_no_match(/missing/, "twice")
+ fedit.write_file
+ expect(edited_file_contents).to eq(append_twice)
+ end
+ end
+
+ describe "file_edited" do
+ it "should return true if a file got edited" do
+ fedit.insert_line_if_no_match(/pattern/, "new line inserted")
+ fedit.write_file
+ expect(fedit.file_edited?).to be_true
+ end
end
end