diff options
262 files changed, 4239 insertions, 4414 deletions
diff --git a/.travis.yml b/.travis.yml index 30e0756539..e25c8535bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,7 +25,7 @@ before_script: branches: only: - master - - 11-stable + - chef-12 env: global: @@ -115,24 +115,6 @@ matrix: - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER ) - cd kitchen-tests script: - - bundle exec kitchen test webapp-ubuntu-1204 - after_failure: - - cat .kitchen/logs/kitchen.log - env: - - UBUNTU=12.04 - - KITCHEN_YAML=.kitchen.travis.yml - - rvm: 2.4.1 - services: docker - sudo: required - gemfile: kitchen-tests/Gemfile - before_install: - - gem update --system $(grep rubygems omnibus_overrides.rb | cut -d'"' -f2) - - gem install bundler -v $(grep bundler omnibus_overrides.rb | cut -d'"' -f2) - bundler_args: --without ci development docgen guard integration maintenance omnibus_package tools aix bsd mac_os_x solaris windows --frozen - before_script: - - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER ) - - cd kitchen-tests - script: - bundle exec kitchen test webapp-ubuntu-1404 after_failure: - cat .kitchen/logs/kitchen.log @@ -188,7 +170,6 @@ matrix: - cd kitchen-tests script: - bundle exec kitchen test webapp-debian-8 - bundler_args: --without ci development docgen guard integration maintenance omnibus_package tools aix bsd mac_os_x solaris windows --frozen after_failure: - cat .kitchen/logs/kitchen.log env: @@ -323,6 +304,25 @@ matrix: - cat /tmp/out.txt - sudo cat /var/log/squid3/cache.log - sudo cat /var/log/squid3/access.log + allow_failures: + - rvm: 2.4.1 + services: docker + sudo: required + gemfile: kitchen-tests/Gemfile + before_install: + - gem update --system $(grep rubygems omnibus_overrides.rb | cut -d'"' -f2) + - gem install bundler -v $(grep bundler omnibus_overrides.rb | cut -d'"' -f2) + bundler_args: --without ci development docgen guard integration maintenance omnibus_package tools aix bsd mac_os_x solaris windows --frozen + before_script: + - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER ) + - cd kitchen-tests + script: + - travis_wait bundle exec kitchen test webapp-amazonlinux + after_failure: + - cat .kitchen/logs/kitchen.log + env: + - AMAZONLINUX=LATEST + - KITCHEN_YAML=.kitchen.travis.yml notifications: on_change: true diff --git a/CHANGELOG.md b/CHANGELOG.md index b356451dfa..3a48982222 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,50 +1,7 @@ This changelog reflects the current state of chef's master branch on github and may not reflect the current released version of chef, which is [![Gem Version](https://badge.fury.io/rb/chef.svg)](https://badge.fury.io/rb/chef). -## [v13.0.78](https://github.com/chef/chef/tree/v13.0.78) (2017-03-30) -[Full Changelog](https://github.com/chef/chef/compare/v12.19.36...v13.0.78) - -**Closed issues:** - -- powershell\_script's default guard\_interpreter should be :powershell\_script [\#5953](https://github.com/chef/chef/issues/5953) -- powershell\_out stdout contains '\r\n' at the end of the output [\#5947](https://github.com/chef/chef/issues/5947) -- `group` resource not idempotent on Windows \(12.19.36\) [\#5857](https://github.com/chef/chef/issues/5857) -- Chef doesn't have access to a file created by itself, under the ownership of SYSTEM? [\#5844](https://github.com/chef/chef/issues/5844) - -**Merged pull requests:** - -- disable building nokogiri on windows for now [\#5958](https://github.com/chef/chef/pull/5958) ([lamont-granquist](https://github.com/lamont-granquist)) -- Chef-13: Make ActionClass a class [\#5952](https://github.com/chef/chef/pull/5952) ([lamont-granquist](https://github.com/lamont-granquist)) -- Fix action class weirdness in Chef-13 [\#5946](https://github.com/chef/chef/pull/5946) ([lamont-granquist](https://github.com/lamont-granquist)) -- Make ResourceReporter smarter to get resource identity and state [\#5941](https://github.com/chef/chef/pull/5941) ([afiune](https://github.com/afiune)) -- Chef-13: Simplify DSL creation [\#5934](https://github.com/chef/chef/pull/5934) ([lamont-granquist](https://github.com/lamont-granquist)) -- Chef-13: Remove deprecated Chef::Client attrs [\#5932](https://github.com/chef/chef/pull/5932) ([lamont-granquist](https://github.com/lamont-granquist)) -- Chef-13: remove method\_missing from the DSL [\#5930](https://github.com/chef/chef/pull/5930) ([lamont-granquist](https://github.com/lamont-granquist)) -- Chef-13: remove node\_map back-compat [\#5926](https://github.com/chef/chef/pull/5926) ([lamont-granquist](https://github.com/lamont-granquist)) -- Chef-13: remove deprecated run\_context methods [\#5925](https://github.com/chef/chef/pull/5925) ([lamont-granquist](https://github.com/lamont-granquist)) -- Chef-13: remove Chef::ShellOut [\#5923](https://github.com/chef/chef/pull/5923) ([lamont-granquist](https://github.com/lamont-granquist)) -- Chef-13: remove more deprecated provider\_resolver code [\#5920](https://github.com/chef/chef/pull/5920) ([lamont-granquist](https://github.com/lamont-granquist)) -- Chef-13: remove old platform mapping code [\#5914](https://github.com/chef/chef/pull/5914) ([lamont-granquist](https://github.com/lamont-granquist)) -- Chef-13: remove the old rake tasks [\#5913](https://github.com/chef/chef/pull/5913) ([lamont-granquist](https://github.com/lamont-granquist)) -- Chef-13: raise on properties redefining inherited methods [\#5912](https://github.com/chef/chef/pull/5912) ([lamont-granquist](https://github.com/lamont-granquist)) -- fix node\#debug\_value access through arrays [\#5911](https://github.com/chef/chef/pull/5911) ([lamont-granquist](https://github.com/lamont-granquist)) -- Chef-13: Nillable properties [\#5907](https://github.com/chef/chef/pull/5907) ([lamont-granquist](https://github.com/lamont-granquist)) -- Chef-13: freeze merged node attribute [\#5905](https://github.com/chef/chef/pull/5905) ([lamont-granquist](https://github.com/lamont-granquist)) -- bump ruby to 2.3.3 [\#5902](https://github.com/chef/chef/pull/5902) ([lamont-granquist](https://github.com/lamont-granquist)) -- Chef-13: Remove declare\_resource create\_if\_missing API [\#5900](https://github.com/chef/chef/pull/5900) ([lamont-granquist](https://github.com/lamont-granquist)) -- Properly use chef-shell in SoloSession by deprecating old behavior into SoloLegacySession [\#5898](https://github.com/chef/chef/pull/5898) ([afiune](https://github.com/afiune)) -- Chef-13: properly deep dup Node\#to\_hash [\#5896](https://github.com/chef/chef/pull/5896) ([lamont-granquist](https://github.com/lamont-granquist)) -- Chef-13: remove method\_missing access to node object. [\#5895](https://github.com/chef/chef/pull/5895) ([lamont-granquist](https://github.com/lamont-granquist)) -- Jeremymv2/cheffs optimizations [\#5890](https://github.com/chef/chef/pull/5890) ([jeremymv2](https://github.com/jeremymv2)) -- Changed EOP to 'EOP' to avoid content expansion [\#5888](https://github.com/chef/chef/pull/5888) ([afiune](https://github.com/afiune)) -- Compress debs and rpms with xz [\#5884](https://github.com/chef/chef/pull/5884) ([thommay](https://github.com/thommay)) -- Chef-13: Chef::Resource cleanup [\#5882](https://github.com/chef/chef/pull/5882) ([lamont-granquist](https://github.com/lamont-granquist)) -- Fix apt\_repository for latest os version 16.10 [\#5874](https://github.com/chef/chef/pull/5874) ([afiune](https://github.com/afiune)) -- Chef-13: convert additional resource methods to properties [\#5871](https://github.com/chef/chef/pull/5871) ([lamont-granquist](https://github.com/lamont-granquist)) -- Add the ability to blacklist attributes from being saved to the chef server [\#5868](https://github.com/chef/chef/pull/5868) ([robmul](https://github.com/robmul)) -- Chef-13: remove supports API from Chef::Resource [\#5863](https://github.com/chef/chef/pull/5863) ([lamont-granquist](https://github.com/lamont-granquist)) -- HTTP: add debug long for non-JSON response [\#5858](https://github.com/chef/chef/pull/5858) ([lamont-granquist](https://github.com/lamont-granquist)) -- Chef-13 remove resource cloning and 3694 warnings [\#5856](https://github.com/chef/chef/pull/5856) ([lamont-granquist](https://github.com/lamont-granquist)) -- Chef-13: support why-run by default [\#5853](https://github.com/chef/chef/pull/5853) ([lamont-granquist](https://github.com/lamont-granquist)) +## [v13.0.117](https://github.com/chef/chef/tree/v13.0.117) (2017-04-12) +[Full Changelog](https://github.com/chef/chef/compare/v13.0.113...v13.0.117) ## [v12.19.33](https://github.com/chef/chef/tree/v12.19.33) (2017-02-16) [Full Changelog](https://github.com/chef/chef/compare/v12.18.31...v12.19.33) diff --git a/Dockerfile b/Dockerfile index cf1f1a64dd..d18adede4b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM busybox MAINTAINER Chef Software, Inc. <docker@chef.io> ARG CHANNEL=stable -ARG VERSION=12.19.36 +ARG VERSION=13.0.113 RUN wget "http://packages.chef.io/files/${CHANNEL}/chef/${VERSION}/el/5/chef-${VERSION}-1.el5.x86_64.rpm" -O /tmp/chef-client.rpm && \ rpm2cpio /tmp/chef-client.rpm | cpio -idmv && \ @@ -11,17 +11,17 @@ source "https://rubygems.org" # of bundler versions prior to 1.12.0 (https://github.com/bundler/bundler/commit/193a14fe5e0d56294c7b370a0e59f93b2c216eed) gem "chef", path: "." -# tracking master of ohai for chef-13.0 development, this should be able to be deleted after release -gem "ohai", git: "https://github.com/chef/ohai.git" +gem "ohai", "~> 13" gem "chef-config", path: File.expand_path("../chef-config", __FILE__) if File.exist?(File.expand_path("../chef-config", __FILE__)) gem "rake" gem "bundler" -gem "cheffish" # required for rspec tests +gem "cheffish", "~> 13" # required for rspec tests group(:omnibus_package) do gem "appbundler" gem "rb-readline" + gem "inspec" # nokogiri has no ruby-2.4 version for windows so it cannot go into our Gemfile.lock # gem "nokogiri", ">= 1.7.1" end diff --git a/Gemfile.lock b/Gemfile.lock index 5d96b6cc18..12db299c07 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/chef/chef-server - revision: e530dd27a696ec154394f360411ae874adc4e3af + revision: 07fea253ceea254af8fbd9c602a04017751cae21 specs: oc-chef-pedant (2.2.0) activesupport (>= 4.2.7.1, < 6.0) @@ -36,25 +36,8 @@ GIT retriable (>= 1.4) GIT - remote: https://github.com/chef/ohai.git - revision: 05cf05cb76ae22af9a8466e0226d0092b74c8414 - specs: - ohai (13.0.0) - chef-config (>= 12.5.0.alpha.1, < 14) - ffi (~> 1.9) - ffi-yajl (~> 2.2) - ipaddress - mixlib-cli - mixlib-config (~> 2.0) - mixlib-log (>= 1.7.1, < 2.0) - mixlib-shellout (~> 2.0) - plist (~> 3.1) - systemu (~> 2.6.4) - wmi-lite (~> 1.0) - -GIT remote: https://github.com/poise/halite.git - revision: 09c4ef7c6b8188ec2b9a929de8d9f6aa3f620553 + revision: b9b3f61682abe1c656f57b7edbbf43d918c5c16b specs: halite (1.5.1.pre) bundler @@ -64,7 +47,7 @@ GIT GIT remote: https://github.com/poise/poise-boiler.git - revision: d547f77ca7335af7943f681e1d0dd68c825cf429 + revision: 40763eaded9384b1d8d3e3db6f883613b0a3ebe2 specs: poise-boiler (1.14.1.pre) bundler @@ -97,7 +80,7 @@ GIT GIT remote: https://github.com/poise/poise.git - revision: c42a55afe2ff897801b274eb47594135ac856b12 + revision: 47cd0e2296fed918b30b0889ed127c824cc32d30 specs: poise (2.7.3.pre) halite (~> 1.0) @@ -113,16 +96,17 @@ GIT PATH remote: . specs: - chef (13.0.79) + chef (13.0.118) addressable bundler (>= 1.10) - chef-config (= 13.0.79) - chef-zero (>= 4.8) + chef-config (= 13.0.118) + chef-zero (>= 13.0) diff-lcs (~> 1.2, >= 1.2.4) erubis (~> 2.7) ffi-yajl (~> 2.2) highline (~> 1.6, >= 1.6.9) iniparse (~> 1.4) + iso8601 (~> 0.9.1) mixlib-archive (~> 0.4) mixlib-authentication (~> 1.4) mixlib-cli (~> 1.7) @@ -142,17 +126,18 @@ PATH specinfra (~> 2.10) syslog-logger (~> 1.6) uuidtools (~> 2.1.5) - chef (13.0.79-universal-mingw32) + chef (13.0.118-universal-mingw32) addressable bundler (>= 1.10) - chef-config (= 13.0.79) - chef-zero (>= 4.8) + chef-config (= 13.0.118) + chef-zero (>= 13.0) diff-lcs (~> 1.2, >= 1.2.4) erubis (~> 2.7) ffi (~> 1.9) ffi-yajl (~> 2.2) highline (~> 1.6, >= 1.6.9) iniparse (~> 1.4) + iso8601 (~> 0.9.1) mixlib-archive (~> 0.4) mixlib-authentication (~> 1.4) mixlib-cli (~> 1.7) @@ -186,7 +171,7 @@ PATH PATH remote: chef-config specs: - chef-config (13.0.79) + chef-config (13.0.118) addressable fuzzyurl mixlib-config (~> 2.0) @@ -205,31 +190,32 @@ GEM mixlib-cli (~> 1.4) artifactory (2.8.1) ast (2.3.0) - aws-sdk (2.8.14) - aws-sdk-resources (= 2.8.14) - aws-sdk-core (2.8.14) + aws-sdk (2.9.5) + aws-sdk-resources (= 2.9.5) + aws-sdk-core (2.9.5) aws-sigv4 (~> 1.0) jmespath (~> 1.0) - aws-sdk-resources (2.8.14) - aws-sdk-core (= 2.8.14) + aws-sdk-resources (2.9.5) + aws-sdk-core (= 2.9.5) aws-sigv4 (1.0.0) backports (3.7.0) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) + blankslate (2.1.2.4) builder (3.2.3) byebug (9.0.6) chef-api (0.7.0) logify (~> 0.1) mime-types chef-sugar (3.4.0) - chef-zero (5.3.2) + chef-zero (13.0.0) ffi-yajl (~> 2.2) hashie (>= 2.0, < 4.0) mixlib-log (~> 1.3) rack (~> 2.0) uuidtools (~> 2.1) - cheffish (5.0.1) - chef-zero (~> 5.0) + cheffish (13.0.0) + chef-zero (~> 13.0) net-ssh chefspec (5.4.0) chef (>= 12.0) @@ -257,13 +243,16 @@ GEM debug_inspector (0.0.2) diff-lcs (1.3) docile (1.1.5) - domain_name (0.5.20170223) + docker-api (1.33.3) + excon (>= 0.38.0) + json + domain_name (0.5.20170404) unf (>= 0.0.5, < 1.0.0) erubis (2.7.0) ethon (0.10.1) ffi (>= 1.3.0) excon (0.55.0) - faraday (0.11.0) + faraday (0.12.0.1) multipart-post (>= 1.2, < 3) faraday-http-cache (2.0.0) faraday (~> 0.8) @@ -309,7 +298,25 @@ GEM httpclient (2.8.3) i18n (0.8.1) iniparse (1.4.2) + inspec (1.19.1) + addressable (~> 2.4) + faraday (>= 0.9.0) + hashie (~> 3.4) + json (>= 1.8, < 3.0) + method_source (~> 0.8) + mixlib-log + parallel (~> 1.9) + pry (~> 0) + rainbow (~> 2) + rspec (~> 3) + rspec-its (~> 1.2) + rubyzip (~> 1.1) + sslshake (~> 1) + thor (~> 0.19) + toml (~> 0.1) + train (>= 0.22.0, < 1.0) ipaddress (0.8.3) + iso8601 (0.9.1) jmespath (1.3.1) json (2.0.3) kitchen-docker (2.6.0) @@ -323,7 +330,7 @@ GEM kitchen-sync (2.1.2) net-sftp test-kitchen (>= 1.0.0) - kitchen-vagrant (1.0.2) + kitchen-vagrant (1.1.0) test-kitchen (~> 1.4) knife-windows (1.9.0) winrm (~> 2.1) @@ -382,10 +389,25 @@ GEM nokogiri (1.7.1-x86-mingw32) mini_portile2 (~> 2.1.0) nori (2.6.0) - octokit (4.6.2) + octokit (4.7.0) sawyer (~> 0.8.0, >= 0.5.3) + ohai (13.0.1) + chef-config (>= 12.5.0.alpha.1, < 14) + ffi (~> 1.9) + ffi-yajl (~> 2.2) + ipaddress + mixlib-cli + mixlib-config (~> 2.0) + mixlib-log (>= 1.7.1, < 2.0) + mixlib-shellout (~> 2.0) + plist (~> 3.1) + systemu (~> 2.6.4) + wmi-lite (~> 1.0) + parallel (1.11.1) parser (2.4.0.0) ast (~> 2.2) + parslet (1.5.0) + blankslate (~> 2.0) plist (3.2.0) poise-profiler (1.0.1) halite (~> 1.0) @@ -477,7 +499,8 @@ GEM net-ssh (>= 2.7, < 5.0) net-telnet sfl - stove (5.0.0) + sslshake (1.1.0) + stove (5.1.0) chef-api (~> 0.5) logify (~> 0.2) syslog-logger (1.6.8) @@ -492,7 +515,17 @@ GEM thor (~> 0.19, < 0.19.2) thor (0.19.1) thread_safe (0.3.6) + toml (0.1.2) + parslet (~> 1.5.0) tomlrb (1.2.4) + train (0.23.0) + docker-api (~> 1.26) + json (>= 1.8, < 3.0) + mixlib-shellout (~> 2.0) + net-scp (~> 1.2) + net-ssh (>= 2.9, < 5.0) + winrm (~> 2.0) + winrm-fs (~> 1.0) travis (1.8.8) backports faraday (~> 0.9) @@ -537,7 +570,7 @@ GEM ffi-win32-extensions windows-api (0.4.4) win32-api (>= 1.4.5) - winrm (2.1.3) + winrm (2.2.1) builder (>= 2.1.2) erubis (~> 2.7) gssapi (~> 1.2) @@ -571,19 +604,20 @@ DEPENDENCIES chef! chef-config! chef-sugar - cheffish + cheffish (~> 13) chefspec chefstyle! cucumber (>= 2.4.0) foodcritic github_changelog_generator! halite! + inspec knife-windows mixlib-install netrc oc-chef-pedant! octokit - ohai! + ohai (~> 13) poise! poise-boiler! pry diff --git a/MAINTAINERS.md b/MAINTAINERS.md index c87879ac16..ae689483c7 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -46,6 +46,7 @@ To mention the team, use @chef/client-core * [Tim Smith](https://github.com/tas50) * [Tom Duffield](https://github.com/tduffield) * [Tyler Ball](https://github.com/tyler-ball) +* [Josh Hudson](https://github.com/itmustbejj) ## Chef Provisioning diff --git a/MAINTAINERS.toml b/MAINTAINERS.toml index 10e7655cd8..83de2c6bc6 100644 --- a/MAINTAINERS.toml +++ b/MAINTAINERS.toml @@ -51,7 +51,8 @@ Maintainers for the Chef client, Ohai, mixlibs, ChefDK, ChefSpec, Foodcritic, ch "stevendanna", "tas50", "tduffield", - "tyler-ball" + "tyler-ball", + "itmustbejj" ] [Org.Components.Provisioning] @@ -366,3 +367,9 @@ The specific components of Chef related to a given platform - including (but not GitHub = "mikedodge04" Twitter = "mikedodge04" IRC = "mikedodge04" + + [people.itmustbejj] + Name = "Josh Hudson" + GitHub = "itmustbejj" + Twitter = "itmustbejj" + IRC = "itmustbejj" diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index a1e5ceab45..e01bfc9097 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,7 +2,101 @@ _This file holds "in progress" release notes for the current release under devel # Chef Client Release Notes 13.0: -## Back Compat Breaks +## Rubygems provider sources behavior changed. + +The behavior of `gem_package` and `chef_gem` is now to always apply the `Chef::Config[:rubygems_uri]` sources, which may be a +String uri or an Array of Strings. If additional sources are put on the resource with the `source` property those are added +to the configured `:rubygems_uri` sources. + +This should enable easier setup of rubygems mirrors particularly in "airgapped" environments through the use of the global config +variable. It also means that an admin may force all rubygems.org traffic to an internal mirror, while still being able to +consume external cookbooks which have resources which add other mirrors unchanged (in a non-airgapped environment). + +In the case where a resource must force the use of only the specified source(s), then the `include_default_source` property +has been added -- setting it to false will remove the `Chef::Config[:rubygems_url]` setting from the list of sources for +that resource. + +The behavior of the `clear_sources` property is now to only add `--clear-sources` and has no magic side effects on the source options. + +## Ruby version upgraded to 2.4.1 + +We've upgraded to the latest stable release of the Ruby programming +language. See the Ruby [2.4.0 Release Notes](https://www.ruby-lang.org/en/news/2016/12/25/ruby-2-4-0-released/) for an overview of what's new in the language. + +## Resource can now declare a default name + +The core `apt_update` resource can now be declared without any name argument, no need for `apt_update "this string doesn't matter but +why do i have to type it?"`. + +This can be used by any other resource by just overriding the name property and supplying a default: + +```ruby + property :name, String, default: "" +``` + +Notifications to resources with empty strings as their name is also supported via either the bare resource name (`apt_update` -- +matches what the user types in the DSL) or with empty brackets (`apt_update[]` -- matches the resource notification pattern). + +## The knife ssh command applies the same fuzzifier as knife search node + +A bare name to knife search node will search for the name in `tags`, `roles`, `fqdn`, `addresses`, `policy_name` or `policy_group` fields and will +match when given partial strings (available since Chef 11). The `knife ssh` search term has been similarly extended so that the +search API matches in both cases. The node search fuzzifier has also been extracted out to a `fuzz` option to Chef::Search::Query for re-use +elsewhere. + +## Cookbook root aliases + +Rather than `attributes/default.rb`, cookbooks can now use `attributes.rb` in +the root of the cookbook. Similarly for a single default recipe, cookbooks can +use `recipe.rb` in the root of the cookbook. + +## knife ssh can now connect to gateways with ssh key authentication + +The new `gateway_identity_file` option allows the operator to specify +the key to access ssh gateways with. + +## Windows Task resource added + +The `windows_task` resource has been ported from the windows cookbook, +and many bugs have been fixed. + +## Solaris SMF services can now been started recursively + +It is now possible to load Solaris services recursively, by ensuring the +new `options` property of the `service` resource contains `-r`. + +## It's now possible to blacklist node attributes + +This is the inverse of the pre-existing whitelisting functionality. + +## The guard interpreter for `powershell_script` is Powershell, again + +When writing `not_if` or `only_if` statements, by default we now run +those statements using powershell, rather than forcing the user to set +`guard_interpreter` each time. + +## Zypper GPG checks by default + +Zypper now defaults to performing gpg checks of packages. + +## The InSpec gem is now shipped by default + +The `inspec` and `train` gems are shipped by default in the chef omnibus +package, making it easier for users in airgapped environments to use +InSpec. + +## Backwards Compatibility Breaks + +### Resource Cloning has been removed + +When Chef compiles resources, it will no longer attempt to merge the +properties of previously compiled resources with the same name and type +in to the new resource. See [the deprecation page](https://docs.chef.io/deprecations_resource_cloning.html) for further information. + +### It is an error to specify both `default` and `name_property` on a property + +Chef 12 made this work by picking the first option it found, but it was +always an error and has now been disallowed. ### The path property of the execute resource has been removed @@ -20,11 +114,11 @@ It is possible that this was being used as a no-op resource, but the log resourc resource added. Omitting the code property or mixing up the code property with the command property are also common usage mistakes that we need to catch and error on. -### The chef_gem resource defaults to not run at compile time +### The chef\_gem resource defaults to not run at compile time The `compile_time true` flag may still be used to force compile time. -### The Chef::Config[:chef_gem_compile_time] config option has been removed +### The Chef::Config[:chef\_gem\_compile\_time] config option has been removed In order to for community cookbooks to behave consistently across all users this optional flag has been removed. @@ -138,22 +232,155 @@ so we have removed support for it. No specific replacement for `pip` is being included with Chef at this time, but a `pip`-based `python_package` resource is available in the [`poise-python`](https://github.com/poise/poise-python) cookbooks. -### Ruby version upgraded to 2.4.1 +### Removal of run_command and popen4 APIs -We've upgraded to the latest stable release of the Ruby programming -language. +All the APIs in chef/mixlib/command have been removed. They were deprecated by mixlib-shellout and the shell_out mixin API. -### Resource can now declare a default name +### Iconv has been removed from the ruby libraries and chef omnibus build -The core `apt_update` resource can now be declared without any name argument, no need for `apt_update "this string doesn't matter but -why do i have to type it?"`. +The ruby Iconv library was replaced by the Encoding library in ruby 1.9.x and since the deprecation of ruby 1.8.7 there has been no need +for the Iconv library but we have carried it forwards as a dependency since removing it might break some chef code out there which used +this library. It has now been removed from the ruby build. This also removes LGPLv3 code from the omnibus build and reduces build +headaches from porting iconv to every platform we ship chef-client on. -This can be used by any other resource by just overriding the name property and supplying a default: +This will also affect nokogiri, but that gem natively supports UTF-8, UTF-16LE/BE, ISO-8851-1(Latin-1), ASCII and "HTML" encodings. Users +who really need to write something like Shift-JIS inside of XML will need to either maintain their own nokogiri installs or will need to +convert to using UTF-8. + +### Deprecated cookbook metadata has been removed + +The `recommends`, `suggests`, `conflicts`, `replaces` and `grouping` +metadata fields are no longer supported, and have been removed, since +they were never used. Chef will ignore them in existing `metadata.rb` +files, but we recommend that you remove them. This was proposed in RFC 85. + +### All unignored cookbook files will now be uploaded. + +We now treat every file under a cookbook directory as belonging to a +cookbook, unless that file is ignored with a `chefignore` file. This is +a change from the previous behaviour where only files in certain +directories, such as `recipes` or `templates`, were treated as special. +This change allows chef to support new classes of files, such as Ohai +plugins or Inspec tests, without having to make changes to the cookbook +format to support them. + +### DSL-based custom resources and providers no longer get module constants + +Up until now, creating a `mycook/resources/thing.rb` would create a `Chef::Resources::MycookThing` name to access the resource class object. +This const is no longer created for resources and providers. You can access resource classes through the resolver API like: ```ruby - property :name, String, default: "" +Chef::Resource.resource_for_node(:mycook_thing, node) ``` -Notifications to resources with empty strings as their name is also supported via either the bare resource name (`apt_update` -- -matches what the user types in the DSL) or with empty brackets (`apt_update[]` -- matches the resource notification pattern). +Accessing a provider class is a bit more complex, as you need a resource against which to run a resolution like so: + +```ruby +Chef::ProviderResolver.new(node, find_resource!("mycook_thing[name]"), :nothing).resolve +``` + +### Default values for resource properties are frozen + +A resource declaring something like: + +```ruby +property :x, default: {} +``` + +will now see the default value set to be immutable. This prevents cases of +modifying the default in one resource affecting others. If you want a per-resource +mutable default value, define it inside a `lazy{}` helper like: + +```ruby +property :x, default: lazy { {} } +``` + +### Resources which later modify their name during creation will have their name changed on the ResourceCollection and notifications + +```ruby +some_resource "name_one" do + name "name_two" +end +``` + +The fix for sending notifications to multipackage resources involved changing the API which inserts resources into the resource collection slightly +so that it no longer directly takes the string which is typed into the DSL but reads the (possibly coerced) name off of the resource after it is +built. The end result is that the above resource will be named `some_resource[name_two]` instead of `some_resource[name_one]`. Note that setting +the name (*not* the `name_property`, but actually renaming the resource) is very uncommon. The fix is to simply name the resource correctly in +the first place (`some_resource "name_two" do ...`) + +### `use_inline_resources` is always enabled + +The `use_inline_resources` provider mode is always enabled when using the +`action :name do ... end` syntax. You can remove the `use_inline_resources` +line. + +### `knife cookbook site vendor` has been removed + +Please use `knife cookbook site install` instead. + +### `knife cookbook create` has been removed + +Please use `chef generate cookbook` from the ChefDK instead. + +### Verify commands no longer support "%{file}" + +Chef has always recommended `%{path}`, and `%{file}` has now been +removed. + +### The `partial_search` recipe method has been removed + +The `partial_search` method has been fully replaced by the +`filter_result` argument to `search`, and has now been removed. + +### The logger and formatter settings are more predictable + +The default now is the formatter. There is no more automatic switching to the logger when logging or when output +is sent to a pipe. The logger needs to be specifically requested with `--force-logger` or it will not show up. + +The `--force-formatter` option does still exist, although it will probably be deprecated in the future. + +If your logfiles switch to the formatter, you need to include `--force-logger` for your daemonized runs. + +Redirecting output to a file with `chef-client > /tmp/chef.out` now captures the same output as invoking it directly on the command +line with no redirection. + +### Path Sanity disabled by default and modified + +The chef client itself no long modifies its `ENV['PATH']` variable directly. When using the `shell_out` API now, in addition to +setting up LANG/LANGUAGE/LC_ALL variables that API will also inject certain system paths and the ruby bindir and gemdirs into +the PATH (or Path on Windows). The `shell_out_with_systems_locale` API still does not mangle any environment variables. During +the Chef-13 lifecycle changes will be made to prep Chef-14 to switch so that `shell_out` by default behaves like +`shell_out_with_systems_locale`. A new flag will get introduced to call `shell_out(..., internal: [true|false])` to either +get the forced locale and path settings ("internal") or not. When that is introduced in Chef 13.x the default will be `true` +(backwards-compat with 13.0) and that default will change in 14.0 to 'false'. + +The PATH changes have also been tweaked so that the ruby bindir and gemdir PATHS are prepended instead of appended to the PATH. +Some system directories are still appended. + +Some examples of changes: + +* `which ruby` in 12.x will return any system ruby and fall back to the embedded ruby if using omnibus +* `which ruby` in 13.x will return any system ruby and will not find the embedded ruby if using omnibus +* `shell_out_with_systems_locale("which ruby")` behaves the same as `which ruby` above +* `shell_out("which ruby")` in 12.x will return any system ruby and fall back to the embedded ruby if using omnibus +* `shell_out("which ruby")` in 13.x will always return the omnibus ruby first (but will find the system ruby if not using omnibus) + +The PATH in `shell_out` can also be overridden: + +* `shell_out("which ruby", env: { "PATH" => nil })` - behaves like shell_out_with_systems_locale() +* `shell_out("which ruby", env: { "PATH" => [...include PATH string here...] })` - set it arbitrarily however you need + +Since most providers which launch custom user commands use `shell_out_with_systems_locale` (service, execute, script, etc) the behavior +will be that those commands that used to be having embedded omnibus paths injected into them no longer will. Generally this will +fix more problems than it solves, but may causes issues for some use cases. + +### Default guard clauses (`not_if`/`only_if`) do not change the PATH or other env vars + +The implementation switched to `shell_out_with_systems_locale` to match `execute` resource, etc. + +### Chef Client will now exit using the RFC062 defined exit codes + +Chef Client will only exit with exit codes defined in RFC 062. This allows other tooling to respond to how a Chef run completes. Attempting to exit Chef Client with an unsupported exit code (either via `Chef::Application.fatal!` or `Chef::Application.exit!`) will result in an exit code of 1 (GENERIC_FAILURE) and a warning in the event log. +When Chef Client is running as a forked process on unix systems, the standardized exit codes are used by the child process. To actually have Chef Client return the standard exit code, `client_fork false` will need to be set in Chef Client's configuration file. @@ -1 +1 @@ -13.0.79
\ No newline at end of file +13.0.118
\ No newline at end of file diff --git a/acceptance/Gemfile.lock b/acceptance/Gemfile.lock index dfb3e68b67..eb139cc225 100644 --- a/acceptance/Gemfile.lock +++ b/acceptance/Gemfile.lock @@ -12,13 +12,13 @@ GEM addressable (2.5.1) public_suffix (~> 2.0, >= 2.0.2) artifactory (2.8.1) - aws-sdk (2.8.14) - aws-sdk-resources (= 2.8.14) - aws-sdk-core (2.8.14) + aws-sdk (2.9.1) + aws-sdk-resources (= 2.9.1) + aws-sdk-core (2.9.1) aws-sigv4 (~> 1.0) jmespath (~> 1.0) - aws-sdk-resources (2.8.14) - aws-sdk-core (= 2.8.14) + aws-sdk-resources (2.9.1) + aws-sdk-core (= 2.9.1) aws-sigv4 (1.0.0) berkshelf (5.6.4) addressable (~> 2.3, >= 2.3.4) diff --git a/acceptance/data-collector/.acceptance/data-collector-test/metadata.rb b/acceptance/data-collector/.acceptance/data-collector-test/metadata.rb index dbd376aa83..34402c8af1 100644 --- a/acceptance/data-collector/.acceptance/data-collector-test/metadata.rb +++ b/acceptance/data-collector/.acceptance/data-collector-test/metadata.rb @@ -1,7 +1,7 @@ name 'data-collector-test' maintainer 'Adam Leff' maintainer_email 'adamleff@chef.io' -license 'Apache 2.0' +license 'Apache-2.0' description 'Installs/Configures data-collector-test' long_description 'Installs/Configures data-collector-test' version '0.1.0' diff --git a/acceptance/data-collector/test/integration/default/serverspec/default_spec.rb b/acceptance/data-collector/test/integration/default/serverspec/default_spec.rb index 59c1f8d21b..040fbcbcca 100644 --- a/acceptance/data-collector/test/integration/default/serverspec/default_spec.rb +++ b/acceptance/data-collector/test/integration/default/serverspec/default_spec.rb @@ -108,6 +108,8 @@ shared_examples_for "run_converge.success payload check" do node node_name organization_name + policy_name + policy_group resources run_id run_list @@ -145,6 +147,8 @@ shared_examples_for "run_converge.failure payload check" do node node_name organization_name + policy_name + policy_group resources run_id run_list diff --git a/appveyor.yml b/appveyor.yml index bb15c065a7..c4d7dfcffc 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -15,6 +15,7 @@ skip_tags: true branches: only: - master + - chef-12 install: - systeminfo diff --git a/chef-config/lib/chef-config/config.rb b/chef-config/lib/chef-config/config.rb index 9166437101..0666dd869d 100644 --- a/chef-config/lib/chef-config/config.rb +++ b/chef-config/lib/chef-config/config.rb @@ -250,7 +250,7 @@ module ChefConfig default(:policy_path) { derive_path_from_chef_repo_path("policies") } # Turn on "path sanity" by default. See also: http://wiki.opscode.com/display/chef/User+Environment+PATH+Sanity - default :enforce_path_sanity, true + default :enforce_path_sanity, false # Formatted Chef Client output is a beta feature, disabled by default: default :formatter, "null" @@ -450,6 +450,10 @@ module ChefConfig # most of our testing scenarios) default :minimal_ohai, false + # When consuming Ohai plugins from cookbook segments, we place those plugins in this directory. + # Subsequent chef client runs will wipe and re-populate the directory to ensure cleanliness + default(:ohai_segment_plugin_path) { PathHelper.join(config_dir, "ohai", "cookbook_plugins") } + ### # Policyfile Settings # @@ -641,11 +645,10 @@ module ChefConfig # generation (server generates client keys). default(:local_key_generation) { true } - # 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. - # Leaving this set to nil or false is a security hazard! - default :zypper_check_gpg, nil + # Zypper package provider gpg checks. Set to false to disable package + # gpg signature checking globally. This will warn you that it is a + # bad thing to do. + default :zypper_check_gpg, true # Report Handlers default :report_handlers, [] @@ -1051,7 +1054,8 @@ module ChefConfig # break Chef community cookbooks and is very highly discouraged. default :ruby_encoding, Encoding::UTF_8 - default :rubygems_url, "https://rubygems.org" + # can be set to a string or array of strings for URIs to set as rubygems sources + default :rubygems_url, "https://www.rubygems.org" # If installed via an omnibus installer, this gives the path to the # "embedded" directory which contains all of the software packaged with diff --git a/chef-config/lib/chef-config/version.rb b/chef-config/lib/chef-config/version.rb index 284c5e6da0..e5105b657e 100644 --- a/chef-config/lib/chef-config/version.rb +++ b/chef-config/lib/chef-config/version.rb @@ -21,7 +21,7 @@ module ChefConfig CHEFCONFIG_ROOT = File.expand_path("../..", __FILE__) - VERSION = "13.0.79" + VERSION = "13.0.118" end # diff --git a/chef.gemspec b/chef.gemspec index a44c882089..93bfd26ea6 100644 --- a/chef.gemspec +++ b/chef.gemspec @@ -32,11 +32,12 @@ 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", ">= 4.8" + s.add_dependency "chef-zero", ">= 13.0" s.add_dependency "plist", "~> 3.2" s.add_dependency "iniparse", "~> 1.4" s.add_dependency "addressable" + s.add_dependency "iso8601", "~> 0.9.1" # Audit mode requires these, so they are non-developmental dependencies now %w{rspec-core rspec-expectations rspec-mocks}.each { |gem| s.add_dependency gem, "~> 3.5" } diff --git a/kitchen-tests/.kitchen.travis.yml b/kitchen-tests/.kitchen.travis.yml index 6a66daffe6..3dcc5c64a7 100644 --- a/kitchen-tests/.kitchen.travis.yml +++ b/kitchen-tests/.kitchen.travis.yml @@ -83,14 +83,6 @@ platforms: - RUN yum makecache - RUN sed -i -e "s/Defaults.*requiretty.*/Defaults !requiretty/g" /etc/sudoers -- name: ubuntu-12.04 - driver: - image: ubuntu-upstart:12.04 - pid_one_command: /sbin/init - intermediate_instructions: - - RUN /usr/bin/apt-get update - - RUN /usr/bin/apt-get -y install zlib1g-dev sudo net-tools wget ca-certificates - - name: ubuntu-14.04 driver: image: ubuntu-upstart:14.04 diff --git a/kitchen-tests/.kitchen.yml b/kitchen-tests/.kitchen.yml index 33841b0b94..87981781db 100644 --- a/kitchen-tests/.kitchen.yml +++ b/kitchen-tests/.kitchen.yml @@ -22,7 +22,6 @@ provisioner: diff_disabled: true platforms: - - name: ubuntu-12.04 - name: ubuntu-14.04 - name: ubuntu-16.04 - name: centos-7.2 diff --git a/kitchen-tests/Berksfile b/kitchen-tests/Berksfile index 09e4cea33a..9a2df463fc 100644 --- a/kitchen-tests/Berksfile +++ b/kitchen-tests/Berksfile @@ -7,7 +7,8 @@ cookbook "php", "~> 1.5.0" cookbook "resolver", github: "chef-cookbooks/resolver" -cookbook "awesome_customers_ubuntu_wrapper", path: "cookbooks/awesome_customers_ubuntu_wrapper" -cookbook "awesome_customers_ubuntu", github: "lamont-granquist/awesome_customers_ubuntu", branch: "lcg/bump-mysql-version" -cookbook "awesome_customers_rhel_wrapper", path: "cookbooks/awesome_customers_rhel_wrapper" -cookbook "awesome_customers_rhel", github: "lamont-granquist/awesome_customers_rhel", branch: "lcg/bump-mysql-version" +# Disabled pending updating these test cases for Chef 13. +# cookbook "awesome_customers_ubuntu_wrapper", path: "cookbooks/awesome_customers_ubuntu_wrapper" +# cookbook "awesome_customers_ubuntu", github: "lamont-granquist/awesome_customers_ubuntu", branch: "lcg/bump-mysql-version" +# cookbook "awesome_customers_rhel_wrapper", path: "cookbooks/awesome_customers_rhel_wrapper" +# cookbook "awesome_customers_rhel", github: "lamont-granquist/awesome_customers_rhel", branch: "lcg/bump-mysql-version" diff --git a/kitchen-tests/Berksfile.lock b/kitchen-tests/Berksfile.lock index 620c23e5ba..3d6e8d15c0 100644 --- a/kitchen-tests/Berksfile.lock +++ b/kitchen-tests/Berksfile.lock @@ -1,16 +1,4 @@ DEPENDENCIES - awesome_customers_rhel - git: https://github.com/lamont-granquist/awesome_customers_rhel.git - revision: ed730957b1b75c8701c708e2deb0740ca8325322 - branch: lcg/bump-mysql-version - awesome_customers_rhel_wrapper - path: cookbooks/awesome_customers_rhel_wrapper - awesome_customers_ubuntu - git: https://github.com/lamont-granquist/awesome_customers_ubuntu.git - revision: 9d40958413d0ed1ef1e68c74d41895158c724964 - branch: lcg/bump-mysql-version - awesome_customers_ubuntu_wrapper - path: cookbooks/awesome_customers_ubuntu_wrapper base path: cookbooks/base php (~> 1.5.0) @@ -19,26 +7,7 @@ DEPENDENCIES revision: 4ab8cb0dfee3696fb8a1e4398e95bff9f33fd6ab GRAPH - apt (4.0.2) - compat_resource (>= 12.10) - awesome_customers_rhel (0.1.0) - database (~> 6.0) - firewall (~> 2.5) - httpd (~> 0.4) - mysql (~> 8.0) - mysql2_chef_gem (~> 1.1) - selinux (~> 0.9) - awesome_customers_rhel_wrapper (0.1.0) - awesome_customers_rhel (>= 0.0.0) - awesome_customers_ubuntu (0.1.0) - apt (~> 4.0) - database (~> 6.0) - firewall (~> 2.5) - httpd (~> 0.4) - mysql (~> 8.0) - mysql2_chef_gem (~> 1.1) - awesome_customers_ubuntu_wrapper (0.1.0) - awesome_customers_ubuntu (>= 0.0.0) + apt (6.0.1) base (0.1.0) apt (>= 0.0.0) build-essential (>= 0.0.0) @@ -57,48 +26,29 @@ GRAPH build-essential (8.0.0) mingw (>= 1.1) seven_zip (>= 0.0.0) - chef-client (7.2.0) + chef-client (8.0.0) cron (>= 1.7.0) logrotate (>= 1.9.0) windows (>= 1.42.0) - chef-sugar (3.4.0) chef_hostname (0.5.0) compat_resource (12.16.3) cron (4.1.0) compat_resource (>= 0.0.0) - database (6.1.1) - postgresql (>= 1.0.0) - firewall (2.5.4) - chef-sugar (>= 0.0.0) - httpd (0.4.5) - compat_resource (>= 12.16.3) iis (5.1.0) windows (>= 2.0) - inifile_chef_gem (0.1.0) - build-essential (>= 0.0.0) - iptables (4.0.0) + iptables (4.0.1) logrotate (2.1.0) compat_resource (>= 0.0.0) - mariadb (1.3.0) - apt (>= 0.0.0) - yum (>= 0.0.0) - yum-epel (>= 0.0.0) - yum-scl (>= 0.0.0) mingw (2.0.0) seven_zip (>= 0.0.0) multipackage (4.0.0) compat_resource (>= 0.0.0) - mysql (8.3.0) - mysql2_chef_gem (1.1.0) - build-essential (>= 0.0.0) - mariadb (>= 0.0.0) - mysql (>= 6.0) + mysql (8.3.1) nscd (5.0.0) ntp (3.3.1) ohai (5.0.2) - openssh (2.1.1) + openssh (2.2.0) iptables (>= 1.0) - openssl (7.0.1) php (1.5.0) build-essential (>= 0.0.0) iis (>= 0.0.0) @@ -106,25 +56,17 @@ GRAPH windows (>= 0.0.0) xml (>= 0.0.0) yum-epel (>= 0.0.0) - postgresql (6.1.1) - build-essential (>= 2.0.0) - compat_resource (>= 12.16.3) - openssl (>= 4.0) resolver (2.0.1) - selinux (0.9.0) + selinux (1.0.3) seven_zip (2.0.2) windows (>= 1.2.2) sudo (3.3.1) ubuntu (2.0.1) apt (>= 0.0.0) users (4.0.3) - windows (3.0.3) + windows (3.0.4) ohai (>= 4.0.0) xml (3.1.1) build-essential (>= 0.0.0) - yum (5.0.0) yum-epel (2.1.1) compat_resource (>= 12.16.3) - yum-scl (0.2.0) - inifile_chef_gem (>= 0.0.0) - yum (>= 0.0.0) diff --git a/kitchen-tests/Gemfile.lock b/kitchen-tests/Gemfile.lock index a9981abe91..8881c6b839 100644 --- a/kitchen-tests/Gemfile.lock +++ b/kitchen-tests/Gemfile.lock @@ -1,16 +1,16 @@ GEM remote: https://rubygems.org/ specs: - addressable (2.5.0) + addressable (2.5.1) public_suffix (~> 2.0, >= 2.0.2) artifactory (2.8.1) - aws-sdk (2.8.13) - aws-sdk-resources (= 2.8.13) - aws-sdk-core (2.8.13) + aws-sdk (2.9.1) + aws-sdk-resources (= 2.9.1) + aws-sdk-core (2.9.1) aws-sigv4 (~> 1.0) jmespath (~> 1.0) - aws-sdk-resources (2.8.13) - aws-sdk-core (= 2.8.13) + aws-sdk-resources (2.9.1) + aws-sdk-core (= 2.9.1) aws-sigv4 (1.0.0) berkshelf (5.6.4) addressable (~> 2.3, >= 2.3.4) @@ -73,7 +73,8 @@ GEM hitimes (1.2.4) hitimes (1.2.4-x86-mingw32) httpclient (2.8.3) - inspec (1.17.0) + inspec (1.18.0) + addressable (~> 2.5) faraday (>= 0.9.0) hashie (~> 3.4) json (>= 1.8, < 3.0) @@ -113,7 +114,7 @@ GEM little-plugger (~> 1.1) multi_json (~> 1.10) method_source (0.8.2) - mini_portile2 (2.1.0) + mini_portile (0.6.2) minitar (0.6.1) mixlib-archive (0.4.1) mixlib-log @@ -140,10 +141,10 @@ GEM net-ssh-gateway (1.3.0) net-ssh (>= 2.6.5) nio4r (2.0.0) - nokogiri (1.7.1) - mini_portile2 (~> 2.1.0) - nokogiri (1.7.1-x86-mingw32) - mini_portile2 (~> 2.1.0) + nokogiri (1.6.6.4) + mini_portile (~> 0.6.0) + nokogiri (1.6.6.4-x86-mingw32) + mini_portile (~> 0.6.0) nori (2.6.0) octokit (4.6.2) sawyer (~> 0.8.0, >= 0.5.3) @@ -203,7 +204,7 @@ GEM solve (3.1.0) molinillo (>= 0.5) semverse (>= 1.1, < 3.0) - sslshake (1.0.13) + sslshake (1.1.0) test-kitchen (1.16.0) mixlib-install (>= 1.2, < 3.0) mixlib-shellout (>= 1.2, < 3.0) @@ -217,7 +218,7 @@ GEM hitimes toml (0.1.2) parslet (~> 1.5.0) - train (0.22.1) + train (0.23.0) docker-api (~> 1.26) json (>= 1.8, < 3.0) mixlib-shellout (~> 2.0) diff --git a/kitchen-tests/cookbooks/audit_test/metadata.rb b/kitchen-tests/cookbooks/audit_test/metadata.rb index 3fbda5dbe1..2b34ce28d0 100644 --- a/kitchen-tests/cookbooks/audit_test/metadata.rb +++ b/kitchen-tests/cookbooks/audit_test/metadata.rb @@ -1,7 +1,7 @@ name "audit_test" maintainer "The Authors" maintainer_email "you@example.com" -license "all_rights" +license "Apache-2.0" description "Installs/Configures audit_test" long_description "Installs/Configures audit_test" version "0.1.0" diff --git a/kitchen-tests/cookbooks/awesome_customers_rhel_wrapper/metadata.rb b/kitchen-tests/cookbooks/awesome_customers_rhel_wrapper/metadata.rb index 641f2f91e3..49dbb50869 100644 --- a/kitchen-tests/cookbooks/awesome_customers_rhel_wrapper/metadata.rb +++ b/kitchen-tests/cookbooks/awesome_customers_rhel_wrapper/metadata.rb @@ -1,7 +1,7 @@ name "awesome_customers_rhel_wrapper" maintainer "The Authors" maintainer_email "you@example.com" -license "all_rights" +license "Apache-2.0" description "Installs/Configures awesome_customers_rhel" long_description "Installs/Configures awesome_customers_rhel" version "0.1.0" diff --git a/kitchen-tests/cookbooks/awesome_customers_rhel_wrapper/recipes/default.rb b/kitchen-tests/cookbooks/awesome_customers_rhel_wrapper/recipes/default.rb index 058ee61617..c58db5fe29 100644 --- a/kitchen-tests/cookbooks/awesome_customers_rhel_wrapper/recipes/default.rb +++ b/kitchen-tests/cookbooks/awesome_customers_rhel_wrapper/recipes/default.rb @@ -1,6 +1,8 @@ # -# Cookbook Name:: awesome_customers_rhel +# Cookbook:: awesome_customers_rhel # Recipe:: default # -# Copyright (c) 2016 The Authors, All Rights Reserved. +# Copyright:: 2014-2017, Chef Software, Inc. +# + include_recipe "awesome_customers_rhel::default" diff --git a/kitchen-tests/cookbooks/awesome_customers_ubuntu_wrapper/metadata.rb b/kitchen-tests/cookbooks/awesome_customers_ubuntu_wrapper/metadata.rb index 429fce2f64..d07a2c2c13 100644 --- a/kitchen-tests/cookbooks/awesome_customers_ubuntu_wrapper/metadata.rb +++ b/kitchen-tests/cookbooks/awesome_customers_ubuntu_wrapper/metadata.rb @@ -1,7 +1,7 @@ name "awesome_customers_ubuntu_wrapper" maintainer "The Authors" maintainer_email "you@example.com" -license "all_rights" +license "Apache-2.0" description "Installs/Configures awesome_customers_ubuntu" long_description "Installs/Configures awesome_customers_ubuntu" version "0.1.0" diff --git a/kitchen-tests/cookbooks/awesome_customers_ubuntu_wrapper/recipes/default.rb b/kitchen-tests/cookbooks/awesome_customers_ubuntu_wrapper/recipes/default.rb index 93012d295f..f6fd388f16 100644 --- a/kitchen-tests/cookbooks/awesome_customers_ubuntu_wrapper/recipes/default.rb +++ b/kitchen-tests/cookbooks/awesome_customers_ubuntu_wrapper/recipes/default.rb @@ -1,6 +1,8 @@ # -# Cookbook Name:: awesome_customers_ubuntu +# Cookbook:: awesome_customers_ubuntu # Recipe:: default # -# Copyright (c) 2016 The Authors, All Rights Reserved. +# Copyright:: 2016-2017, Chef Software, Inc. +# + include_recipe "awesome_customers_ubuntu::default" diff --git a/kitchen-tests/cookbooks/base/metadata.rb b/kitchen-tests/cookbooks/base/metadata.rb index 32ea03916f..d29665d598 100644 --- a/kitchen-tests/cookbooks/base/metadata.rb +++ b/kitchen-tests/cookbooks/base/metadata.rb @@ -1,7 +1,7 @@ name "base" maintainer "" maintainer_email "" -license "" +license "Apache-2.0" description "Installs/Configures base" long_description "Installs/Configures base" version "0.1.0" diff --git a/kitchen-tests/cookbooks/base/recipes/default.rb b/kitchen-tests/cookbooks/base/recipes/default.rb index c25673bef5..d8dbb9fe41 100644 --- a/kitchen-tests/cookbooks/base/recipes/default.rb +++ b/kitchen-tests/cookbooks/base/recipes/default.rb @@ -1,8 +1,8 @@ # -# Cookbook Name:: webapp +# Cookbook:: webapp # Recipe:: default # -# Copyright (C) 2014 +# Copyright:: 2014-2017, Chef Software, Inc. # hostname "chef-travis-ci.chef.io" diff --git a/kitchen-tests/cookbooks/base/recipes/packages.rb b/kitchen-tests/cookbooks/base/recipes/packages.rb index c4457e5945..bb5fa7bb71 100644 --- a/kitchen-tests/cookbooks/base/recipes/packages.rb +++ b/kitchen-tests/cookbooks/base/recipes/packages.rb @@ -1,3 +1,9 @@ +# +# Cookbook:: webapp +# Recipe:: packages +# +# Copyright:: 2014-2017, Chef Software, Inc. +# # this is just a list of package that exist on every O/S we test, and often aren't installed by default. you don't # have to get too clever here, you can delete packages if they don't exist everywhere we test. diff --git a/kitchen-tests/cookbooks/base/recipes/tests.rb b/kitchen-tests/cookbooks/base/recipes/tests.rb index 9d9d813865..8b90b48c5d 100644 --- a/kitchen-tests/cookbooks/base/recipes/tests.rb +++ b/kitchen-tests/cookbooks/base/recipes/tests.rb @@ -1,8 +1,8 @@ # -# Cookbook Name:: webapp -# Recipe:: default +# Cookbook:: webapp +# Recipe:: tests # -# Copyright (C) 2014 +# Copyright:: 2014-2017, Chef Software, Inc. # # diff --git a/kitchen-tests/cookbooks/webapp/metadata.rb b/kitchen-tests/cookbooks/webapp/metadata.rb index 5124aa4f6f..f560159fd8 100644 --- a/kitchen-tests/cookbooks/webapp/metadata.rb +++ b/kitchen-tests/cookbooks/webapp/metadata.rb @@ -1,7 +1,7 @@ name "webapp" maintainer "" maintainer_email "" -license "" +license "Apache-2.0" description "Installs/Configures webapp" long_description "Installs/Configures webapp" version "0.1.0" diff --git a/kitchen-tests/cookbooks/webapp/templates/default/index.html.erb b/kitchen-tests/cookbooks/webapp/templates/index.html.erb index 6da0629b9e..6da0629b9e 100644 --- a/kitchen-tests/cookbooks/webapp/templates/default/index.html.erb +++ b/kitchen-tests/cookbooks/webapp/templates/index.html.erb diff --git a/kitchen-tests/cookbooks/webapp/templates/default/index.php.erb b/kitchen-tests/cookbooks/webapp/templates/index.php.erb index b08b076614..b08b076614 100644 --- a/kitchen-tests/cookbooks/webapp/templates/default/index.php.erb +++ b/kitchen-tests/cookbooks/webapp/templates/index.php.erb diff --git a/lib/chef/application.rb b/lib/chef/application.rb index 86078300c2..096ce9c392 100644 --- a/lib/chef/application.rb +++ b/lib/chef/application.rb @@ -48,6 +48,7 @@ class Chef configure_chef configure_logging configure_encoding + emit_warnings end # Get this party started @@ -79,6 +80,10 @@ class Chef end end + def emit_warnings + Chef::Log.warn "Chef::Config[:zypper_check_gpg] is set to false which disables security checking on zypper packages" unless Chef::Config[:zypper_check_gpg] + end + # Parse configuration (options and config file) def configure_chef parse_options @@ -139,16 +144,9 @@ class Chef # unattended, the `force_formatter` option is provided. # # === Auto Log Level - # When `log_level` is set to `:auto` (default), the log level will be `:warn` - # when the primary output mode is an output formatter (see - # +using_output_formatter?+) and `:info` otherwise. + # The `log_level` of `:auto` means `:warn` in the formatter and `:info` in + # the logger. # - # === Automatic STDOUT Logging - # When `force_logger` is configured (e.g., Chef 10 mode), a second logger - # with output on STDOUT is added when running in a console (STDOUT is a tty) - # and the configured log_location isn't STDOUT. This accounts for the case - # that a user has configured a log_location in client.rb, but is running - # chef-client by hand to troubleshoot a problem. def configure_logging configure_log_location Chef::Log.init(MonoLogger.new(Chef::Config[:log_location])) @@ -175,22 +173,21 @@ class Chef end end + # Based on config and whether or not STDOUT is a tty, should we setup a + # secondary logger for stdout? + def want_additional_logger? + ( Chef::Config[:log_location] != STDOUT ) && STDOUT.tty? && !Chef::Config[:daemonize] + end + def configure_stdout_logger stdout_logger = MonoLogger.new(STDOUT) stdout_logger.formatter = Chef::Log.logger.formatter Chef::Log.loggers << stdout_logger end - # Based on config and whether or not STDOUT is a tty, should we setup a - # secondary logger for stdout? - def want_additional_logger? - ( Chef::Config[:log_location] != STDOUT ) && STDOUT.tty? && (!Chef::Config[:daemonize]) && (Chef::Config[:force_logger]) - end - - # Use of output formatters is assumed if `force_formatter` is set or if - # `force_logger` is not set and STDOUT is to a console (tty) + # Use of output formatters is assumed if `force_formatter` is set or if `force_logger` is not set def using_output_formatter? - Chef::Config[:force_formatter] || (!Chef::Config[:force_logger] && STDOUT.tty?) + Chef::Config[:force_formatter] || !Chef::Config[:force_logger] end def auto_log_level? diff --git a/lib/chef/application/exit_code.rb b/lib/chef/application/exit_code.rb index 610a356a7c..917aa16e62 100644 --- a/lib/chef/application/exit_code.rb +++ b/lib/chef/application/exit_code.rb @@ -45,47 +45,17 @@ class Chef class << self def normalize_exit_code(exit_code = nil) - if normalization_not_configured? - normalize_legacy_exit_code_with_warning(exit_code) - elsif normalization_disabled? - normalize_legacy_exit_code(exit_code) + normalized_exit_code = normalize_legacy_exit_code(exit_code) + if valid_exit_codes.include? normalized_exit_code + normalized_exit_code else - normalize_exit_code_to_rfc(exit_code) + Chef::Log.warn(non_standard_exit_code_warning(normalized_exit_code)) + VALID_RFC_062_EXIT_CODES[:GENERIC_FAILURE] end end - def enforce_rfc_062_exit_codes? - !normalization_disabled? && !normalization_not_configured? - end - - def notify_reboot_exit_code_deprecation - return if normalization_disabled? - notify_on_deprecation(reboot_deprecation_warning) - end - - def notify_deprecated_exit_code - return if normalization_disabled? - notify_on_deprecation(deprecation_warning) - end - private - def normalization_disabled? - Chef::Config[:exit_status] == :disabled - end - - def normalization_not_configured? - Chef::Config[:exit_status].nil? - end - - def normalize_legacy_exit_code_with_warning(exit_code) - normalized_exit_code = normalize_legacy_exit_code(exit_code) - unless valid_exit_codes.include? normalized_exit_code - notify_on_deprecation(deprecation_warning) - end - normalized_exit_code - end - def normalize_legacy_exit_code(exit_code) case exit_code when Integer @@ -93,15 +63,6 @@ class Chef when Exception lookup_exit_code_by_exception(exit_code) else - default_exit_code - end - end - - def normalize_exit_code_to_rfc(exit_code) - normalized_exit_code = normalize_legacy_exit_code_with_warning(exit_code) - if valid_exit_codes.include? normalized_exit_code - normalized_exit_code - else VALID_RFC_062_EXIT_CODES[:GENERIC_FAILURE] end end @@ -111,15 +72,6 @@ class Chef VALID_RFC_062_EXIT_CODES[:SIGINT_RECEIVED] elsif sigterm_received?(exception) VALID_RFC_062_EXIT_CODES[:SIGTERM_RECEIVED] - elsif normalization_disabled? || normalization_not_configured? - if legacy_exit_code?(exception) - # We have lots of "Chef::Application.fatal!('', 2) - # This maintains that behavior at initial introduction - # and when the RFC exit_status compliance is disabled. - VALID_RFC_062_EXIT_CODES[:SIGINT_RECEIVED] - else - VALID_RFC_062_EXIT_CODES[:GENERIC_FAILURE] - end elsif reboot_scheduled?(exception) VALID_RFC_062_EXIT_CODES[:REBOOT_SCHEDULED] elsif reboot_needed?(exception) @@ -135,12 +87,6 @@ class Chef end end - def legacy_exit_code?(exception) - resolve_exception_array(exception).any? do |e| - e.is_a? Chef::Exceptions::DeprecatedExitCode - end - end - def reboot_scheduled?(exception) resolve_exception_array(exception).any? do |e| e.is_a? Chef::Exceptions::Reboot @@ -204,26 +150,11 @@ class Chef # the current exit code assignment. end - def deprecation_warning - "Chef RFC 062 (https://github.com/chef/chef-rfc/blob/master/rfc062-exit-status.md) defines the" \ + def non_standard_exit_code_warning(exit_code) + "Chef attempted to exit with a non-standard exit code of #{exit_code}." \ + " Chef RFC 062 (https://github.com/chef/chef-rfc/blob/master/rfc062-exit-status.md) defines the" \ " exit codes that should be used with Chef. Chef::Application::ExitCode defines valid exit codes" \ - " In a future release, non-standard exit codes will be redefined as" \ - " GENERIC_FAILURE unless `exit_status` is set to `:disabled` in your client.rb." - end - - def reboot_deprecation_warning - "Per RFC 062 (https://github.com/chef/chef-rfc/blob/master/rfc062-exit-status.md)" \ - ", when a reboot is requested Chef Client will exit with an exit code of 35, REBOOT_SCHEDULED." \ - " To maintain the current behavior (an exit code of 0), you will need to set `exit_status` to" \ - " `:disabled` in your client.rb" - end - - def default_exit_code - if normalization_disabled? || normalization_not_configured? - DEPRECATED_RFC_062_EXIT_CODES[:DEPRECATED_FAILURE] - else - VALID_RFC_062_EXIT_CODES[:GENERIC_FAILURE] - end + " Non-standard exit codes are redefined as GENERIC_FAILURE." end end diff --git a/lib/chef/application/windows_service.rb b/lib/chef/application/windows_service.rb index 7bc68a586d..fa4b3fd27e 100644 --- a/lib/chef/application/windows_service.rb +++ b/lib/chef/application/windows_service.rb @@ -1,6 +1,6 @@ # # Author:: Christopher Maier (<maier@lambda.local>) -# Copyright:: Copyright 2011-2016, Chef Software, Inc. +# Copyright:: Copyright 2011-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -256,13 +256,13 @@ class Chef # Based on config and whether or not STDOUT is a tty, should we setup a # secondary logger for stdout? def want_additional_logger? - ( Chef::Config[:log_location] != STDOUT ) && STDOUT.tty? && (!Chef::Config[:daemonize]) && (Chef::Config[:force_logger]) + ( Chef::Config[:log_location] != STDOUT ) && STDOUT.tty? && !Chef::Config[:daemonize] end # Use of output formatters is assumed if `force_formatter` is set or if - # `force_logger` is not set and STDOUT is to a console (tty) + # `force_logger` is not set def using_output_formatter? - Chef::Config[:force_formatter] || (!Chef::Config[:force_logger] && STDOUT.tty?) + Chef::Config[:force_formatter] || !Chef::Config[:force_logger] end def auto_log_level? @@ -318,11 +318,11 @@ class Chef Chef::Config.merge!(config) rescue SocketError - Chef::Application.fatal!("Error getting config file #{Chef::Config[:config_file]}", Chef::Exceptions::DeprecatedExitCode.new) + Chef::Application.fatal!("Error getting config file #{Chef::Config[:config_file]}") rescue Chef::Exceptions::ConfigurationError => error - Chef::Application.fatal!("Error processing config file #{Chef::Config[:config_file]} with error #{error.message}", Chef::Exceptions::DeprecatedExitCode.new) + Chef::Application.fatal!("Error processing config file #{Chef::Config[:config_file]} with error #{error.message}") rescue Exception => error - Chef::Application.fatal!("Unknown error processing config file #{Chef::Config[:config_file]} with error #{error.message}", Chef::Exceptions::DeprecatedExitCode.new) + Chef::Application.fatal!("Unknown error processing config file #{Chef::Config[:config_file]} with error #{error.message}") end end diff --git a/lib/chef/chef_fs/chef_fs_data_store.rb b/lib/chef/chef_fs/chef_fs_data_store.rb index 46fe5c4dd3..0c8f12f1be 100644 --- a/lib/chef/chef_fs/chef_fs_data_store.rb +++ b/lib/chef/chef_fs/chef_fs_data_store.rb @@ -768,7 +768,7 @@ class Chef end elsif path.length == 2 && path[0] != "cookbooks" - path[1] = path[1][0..-6] + path[1] = path[1].gsub(/\.(rb|json)/, "") end path diff --git a/lib/chef/chef_fs/file_system/chef_server/chef_server_root_dir.rb b/lib/chef/chef_fs/file_system/chef_server/chef_server_root_dir.rb index 5030a0733f..63ce71ef40 100644 --- a/lib/chef/chef_fs/file_system/chef_server/chef_server_root_dir.rb +++ b/lib/chef/chef_fs/file_system/chef_server/chef_server_root_dir.rb @@ -103,11 +103,11 @@ class Chef end def get_json(path) - Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key, :api_version => "0").get(path) + chef_rest.get(path) end def chef_rest - Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key) + Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key, :api_version => "0") end def api_path diff --git a/lib/chef/chef_fs/file_system/chef_server/cookbook_artifacts_dir.rb b/lib/chef/chef_fs/file_system/chef_server/cookbook_artifacts_dir.rb index 0b82a64a0a..e4df7858a7 100644 --- a/lib/chef/chef_fs/file_system/chef_server/cookbook_artifacts_dir.rb +++ b/lib/chef/chef_fs/file_system/chef_server/cookbook_artifacts_dir.rb @@ -56,7 +56,7 @@ class Chef # to make this work. So instead, we make a temporary cookbook # symlinking back to real cookbook, and upload the proxy. def upload_cookbook(other, options) - cookbook_name, dash, identifier = other.name.rpartition("-") + cookbook_name, _, identifier = other.name.rpartition("-") Dir.mktmpdir do |temp_cookbooks_path| proxy_cookbook_path = "#{temp_cookbooks_path}/#{cookbook_name}" @@ -73,7 +73,7 @@ class Chef cookbook_to_upload.freeze_version if options[:freeze] # Instantiate a new uploader based on the proxy loader - uploader = Chef::CookbookUploader.new(cookbook_to_upload, force: options[:force], rest: root.chef_rest, policy_mode: true) + uploader = Chef::CookbookUploader.new(cookbook_to_upload, force: options[:force], rest: chef_rest, policy_mode: true) with_actual_cookbooks_dir(temp_cookbooks_path) do uploader.upload_cookbooks @@ -92,6 +92,10 @@ class Chef end end + def chef_rest + Chef::ServerAPI.new(root.chef_rest.url, root.chef_rest.options.merge(version_class: Chef::CookbookManifestVersions)) + end + def can_have_child?(name, is_dir) is_dir && name.include?("-") end diff --git a/lib/chef/chef_fs/file_system/chef_server/cookbook_dir.rb b/lib/chef/chef_fs/file_system/chef_server/cookbook_dir.rb index 8f5faf2183..64488ed705 100644 --- a/lib/chef/chef_fs/file_system/chef_server/cookbook_dir.rb +++ b/lib/chef/chef_fs/file_system/chef_server/cookbook_dir.rb @@ -49,18 +49,6 @@ class Chef attr_reader :cookbook_name, :version - COOKBOOK_SEGMENT_INFO = { - :attributes => { :ruby_only => true }, - :definitions => { :ruby_only => true }, - :recipes => { :ruby_only => true }, - :libraries => { :recursive => true }, - :templates => { :recursive => true }, - :files => { :recursive => true }, - :resources => { :ruby_only => true, :recursive => true }, - :providers => { :ruby_only => true, :recursive => true }, - :root_files => {}, - } - def add_child(child) @children << child end @@ -80,34 +68,29 @@ class Chef end def can_have_child?(name, is_dir) - # A cookbook's root may not have directories unless they are segment directories - return name != "root_files" && COOKBOOK_SEGMENT_INFO.keys.include?(name.to_sym) if is_dir + return name != "root_files" if is_dir true end def children if @children.nil? @children = [] - manifest = chef_object.manifest - COOKBOOK_SEGMENT_INFO.each do |segment, segment_info| - next unless manifest.has_key?(segment) - - # Go through each file in the manifest for the segment, and - # add cookbook subdirs and files for it. - manifest[segment].each do |segment_file| - parts = segment_file[:path].split("/") + manifest = chef_object.cookbook_manifest + manifest.by_parent_directory.each do |segment, files| + files.each do |file| + parts = file[:path].split("/") # Get or create the path to the file container = self parts[0, parts.length - 1].each do |part| old_container = container container = old_container.children.find { |child| part == child.name } if !container - container = CookbookSubdir.new(part, old_container, segment_info[:ruby_only], segment_info[:recursive]) + container = CookbookSubdir.new(part, old_container, false, true) old_container.add_child(container) end end # Create the file itself - container.add_child(CookbookFile.new(parts[parts.length - 1], container, segment_file)) + container.add_child(CookbookFile.new(parts[parts.length - 1], container, file)) end end @children = @children.sort_by { |c| c.name } @@ -165,7 +148,11 @@ class Chef end def rest - parent.rest + Chef::ServerAPI.new(parent.rest.url, parent.rest.options.merge(version_class: Chef::CookbookManifestVersions)) + end + + def chef_rest + Chef::ServerAPI.new(parent.chef_rest.url, parent.chef_rest.options.merge(version_class: Chef::CookbookManifestVersions)) end def chef_object @@ -187,7 +174,7 @@ class Chef old_retry_count = Chef::Config[:http_retry_count] begin Chef::Config[:http_retry_count] = 0 - @chef_object ||= Chef::CookbookVersion.from_hash(root.get_json(api_path)) + @chef_object ||= Chef::CookbookVersion.from_hash(chef_rest.get(api_path)) ensure Chef::Config[:http_retry_count] = old_retry_count end diff --git a/lib/chef/chef_fs/file_system/chef_server/cookbooks_dir.rb b/lib/chef/chef_fs/file_system/chef_server/cookbooks_dir.rb index 631562d7ef..4e8e68e364 100644 --- a/lib/chef/chef_fs/file_system/chef_server/cookbooks_dir.rb +++ b/lib/chef/chef_fs/file_system/chef_server/cookbooks_dir.rb @@ -74,13 +74,17 @@ class Chef def upload_cookbook(other, options) cookbook_to_upload = other.chef_object cookbook_to_upload.freeze_version if options[:freeze] - uploader = Chef::CookbookUploader.new(cookbook_to_upload, :force => options[:force], :rest => root.chef_rest) + uploader = Chef::CookbookUploader.new(cookbook_to_upload, :force => options[:force], :rest => chef_rest) with_actual_cookbooks_dir(other.parent.file_path) do uploader.upload_cookbooks end end + def chef_rest + Chef::ServerAPI.new(root.chef_rest.url, root.chef_rest.options.merge(version_class: Chef::CookbookManifestVersions)) + end + # Work around the fact that CookbookUploader doesn't understand chef_repo_path (yet) def with_actual_cookbooks_dir(actual_cookbook_path) old_cookbook_path = Chef::Config.cookbook_path diff --git a/lib/chef/chef_fs/file_system/chef_server/versioned_cookbooks_dir.rb b/lib/chef/chef_fs/file_system/chef_server/versioned_cookbooks_dir.rb index 172405763a..8da3718136 100644 --- a/lib/chef/chef_fs/file_system/chef_server/versioned_cookbooks_dir.rb +++ b/lib/chef/chef_fs/file_system/chef_server/versioned_cookbooks_dir.rb @@ -78,7 +78,7 @@ class Chef cookbook_to_upload.freeze_version if options[:freeze] # Instantiate a new uploader based on the proxy loader - uploader = Chef::CookbookUploader.new(cookbook_to_upload, :force => options[:force], :rest => root.chef_rest) + uploader = Chef::CookbookUploader.new(cookbook_to_upload, :force => options[:force], :rest => chef_rest) with_actual_cookbooks_dir(temp_cookbooks_path) do uploader.upload_cookbooks @@ -97,6 +97,10 @@ class Chef end end + def chef_rest + Chef::ServerAPI.new(root.chef_rest.url, root.chef_rest.options.merge(version_class: Chef::CookbookManifestVersions)) + end + def can_have_child?(name, is_dir) is_dir && name =~ Chef::ChefFS::FileSystem::ChefServer::VersionedCookbookDir::VALID_VERSIONED_COOKBOOK_NAME end diff --git a/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir.rb b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir.rb index 31b538b9ce..b296901dd1 100644 --- a/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir.rb +++ b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir.rb @@ -97,9 +97,9 @@ class Chef end def can_have_child?(name, is_dir) - if is_dir + if is_dir && !%w{ root_files .. . }.include?(name) # Only the given directories will be uploaded. - return Chef::ChefFS::FileSystem::ChefServer::CookbookDir::COOKBOOK_SEGMENT_INFO.keys.include?(name.to_sym) && name != "root_files" + return true elsif name == Chef::Cookbook::CookbookVersionLoader::UPLOADED_COOKBOOK_VERSION_FILE return false end @@ -128,8 +128,7 @@ class Chef protected def make_child_entry(child_name) - segment_info = Chef::ChefFS::FileSystem::ChefServer::CookbookDir::COOKBOOK_SEGMENT_INFO[child_name.to_sym] || {} - ChefRepositoryFileSystemCookbookEntry.new(child_name, self, nil, segment_info[:ruby_only], segment_info[:recursive]) + ChefRepositoryFileSystemCookbookEntry.new(child_name, self, nil, false, true) end def cookbook_version diff --git a/lib/chef/client.rb b/lib/chef/client.rb index b219fdfdd6..9f8a34e85e 100644 --- a/lib/chef/client.rb +++ b/lib/chef/client.rb @@ -280,6 +280,8 @@ class Chef run_context = setup_run_context + load_required_recipe(@rest, run_context) unless Chef::Config[:solo_legacy_mode] + if Chef::Config[:audit_mode] != :audit_only converge_error = converge_and_save(run_context) end @@ -372,7 +374,7 @@ class Chef # @api private def default_formatter - if (STDOUT.tty? && !Chef::Config[:force_logger]) || Chef::Config[:force_formatter] + if !Chef::Config[:force_logger] || Chef::Config[:force_formatter] [:doc] else [:null] @@ -515,6 +517,49 @@ class Chef end # + # Adds a required recipe as specified by the Chef Server + # + # @return The modified run context + # + # @api private + # + # TODO: @rest doesn't appear to be used anywhere outside + # of client.register except for here. If it's common practice + # to create your own rest client, perhaps we should do that + # here but it seems more appropriate to reuse one that we + # know is already created. for ease of testing, we'll pass + # the existing rest client in as a parameter + # + def load_required_recipe(rest, run_context) + required_recipe_contents = rest.get("required_recipe") + Chef::Log.info("Required Recipe found, loading it") + Chef::FileCache.store("required_recipe", required_recipe_contents) + required_recipe_file = Chef::FileCache.load("required_recipe", false) + + # TODO: add integration tests with resource reporting turned on + # (presumably requires changes to chef-zero) + # + # Chef::Recipe.new takes a cookbook name and a recipe name along + # with the run context. These names are eventually used in the + # resource reporter, and if the cookbook name cannot be found in the + # cookbook collection then we will fail with an exception. Cases where + # we currently also fail: + # - specific recipes + # - chef-apply would fail if resource reporting was enabled + # + recipe = Chef::Recipe.new(nil, nil, run_context) + recipe.from_file(required_recipe_file) + run_context + rescue Net::HTTPServerException => e + case e.response + when Net::HTTPNotFound + Chef::Log.debug("Required Recipe not configured on the server, skipping it") + else + raise + end + end + + # # The PolicyBuilder strategy for figuring out run list and cookbooks. # # @return [Chef::PolicyBuilder::Policyfile, Chef::PolicyBuilder::ExpandNodeObject] diff --git a/lib/chef/config_fetcher.rb b/lib/chef/config_fetcher.rb index ee1b64956a..e14428157c 100644 --- a/lib/chef/config_fetcher.rb +++ b/lib/chef/config_fetcher.rb @@ -25,7 +25,7 @@ class Chef begin Chef::JSONCompat.from_json(config_data) rescue Chef::Exceptions::JSON::ParseError => error - Chef::Application.fatal!("Could not parse the provided JSON file (#{config_location}): " + error.message, Chef::Exceptions::DeprecatedExitCode.new) + Chef::Application.fatal!("Could not parse the provided JSON file (#{config_location}): " + error.message) end end @@ -40,15 +40,15 @@ class Chef def fetch_remote_config http.get("") rescue SocketError, SystemCallError, Net::HTTPServerException => error - Chef::Application.fatal!("Cannot fetch config '#{config_location}': '#{error.class}: #{error.message}", Chef::Exceptions::DeprecatedExitCode.new) + Chef::Application.fatal!("Cannot fetch config '#{config_location}': '#{error.class}: #{error.message}") end def read_local_config ::File.read(config_location) rescue Errno::ENOENT - Chef::Application.fatal!("Cannot load configuration from #{config_location}", Chef::Exceptions::DeprecatedExitCode.new) + Chef::Application.fatal!("Cannot load configuration from #{config_location}") rescue Errno::EACCES - Chef::Application.fatal!("Permissions are incorrect on #{config_location}. Please chmod a+r #{config_location}", Chef::Exceptions::DeprecatedExitCode.new) + Chef::Application.fatal!("Permissions are incorrect on #{config_location}. Please chmod a+r #{config_location}") end def config_missing? diff --git a/lib/chef/cookbook/cookbook_version_loader.rb b/lib/chef/cookbook/cookbook_version_loader.rb index b9de9a7482..35dac27fa5 100644 --- a/lib/chef/cookbook/cookbook_version_loader.rb +++ b/lib/chef/cookbook/cookbook_version_loader.rb @@ -9,15 +9,6 @@ class Chef class Cookbook class CookbookVersionLoader - FILETYPES_SUBJECT_TO_IGNORE = [ :attribute_filenames, - :definition_filenames, - :recipe_filenames, - :template_filenames, - :file_filenames, - :library_filenames, - :resource_filenames, - :provider_filenames] - UPLOADED_COOKBOOK_VERSION_FILE = ".uploaded-cookbook-version.json".freeze attr_reader :cookbook_settings @@ -44,16 +35,7 @@ class Chef @relative_path = /#{Regexp.escape(@cookbook_path)}\/(.+)$/ @metadata_loaded = false @cookbook_settings = { - :all_files => {}, - :attribute_filenames => {}, - :definition_filenames => {}, - :recipe_filenames => {}, - :template_filenames => {}, - :file_filenames => {}, - :library_filenames => {}, - :resource_filenames => {}, - :provider_filenames => {}, - :root_filenames => {}, + :all_files => {}, } @metadata_filenames = [] @@ -84,16 +66,6 @@ class Chef remove_ignored_files - load_as(:attribute_filenames, "attributes", "*.rb") - load_as(:definition_filenames, "definitions", "*.rb") - load_as(:recipe_filenames, "recipes", "*.rb") - load_recursively_as(:library_filenames, "libraries", "*") - load_recursively_as(:template_filenames, "templates", "*") - load_recursively_as(:file_filenames, "files", "*") - load_recursively_as(:resource_filenames, "resources", "*.rb") - load_recursively_as(:provider_filenames, "providers", "*.rb") - load_root_files - if empty? Chef::Log.warn "Found a directory #{cookbook_name} in the cookbook path, but it contains no cookbook files. skipping." end @@ -126,16 +98,6 @@ class Chef Chef::CookbookVersion.new(cookbook_name, *cookbook_paths).tap do |c| c.all_files = cookbook_settings[:all_files].values - c.attribute_filenames = cookbook_settings[:attribute_filenames].values - c.definition_filenames = cookbook_settings[:definition_filenames].values - c.recipe_filenames = cookbook_settings[:recipe_filenames].values - c.template_filenames = cookbook_settings[:template_filenames].values - c.file_filenames = cookbook_settings[:file_filenames].values - c.library_filenames = cookbook_settings[:library_filenames].values - c.resource_filenames = cookbook_settings[:resource_filenames].values - c.provider_filenames = cookbook_settings[:provider_filenames].values - c.root_filenames = cookbook_settings[:root_filenames].values - c.metadata_filenames = metadata_filenames c.metadata = metadata c.freeze_version if @frozen @@ -253,51 +215,6 @@ class Chef end end - def load_root_files - select_files_by_glob(File.join(Chef::Util::PathHelper.escape_glob_dir(cookbook_path), "*"), File::FNM_DOTMATCH).each do |file| - file = Chef::Util::PathHelper.cleanpath(file) - next if File.directory?(file) - next if File.basename(file) == UPLOADED_COOKBOOK_VERSION_FILE - name = Chef::Util::PathHelper.relative_path_from(@cookbook_path, file) - cookbook_settings[:root_filenames][name] = file - end - end - - def load_recursively_as(category, category_dir, glob) - glob_pattern = File.join(Chef::Util::PathHelper.escape_glob_dir(cookbook_path, category_dir), "**", glob) - select_files_by_glob(glob_pattern, File::FNM_DOTMATCH).each do |file| - file = Chef::Util::PathHelper.cleanpath(file) - name = Chef::Util::PathHelper.relative_path_from(@cookbook_path, file) - cookbook_settings[category][name] = file - end - end - - def load_as(category, *path_glob) - glob_pattern = File.join(Chef::Util::PathHelper.escape_glob_dir(cookbook_path), *path_glob) - select_files_by_glob(glob_pattern).each do |file| - file = Chef::Util::PathHelper.cleanpath(file) - name = Chef::Util::PathHelper.relative_path_from(@cookbook_path, file) - cookbook_settings[category][name] = file - end - end - - # Mimic Dir.glob inside a cookbook by running `File.fnmatch?` against - # `cookbook_settings[:all_files]`. - # - # @param pattern [String] a glob string passed to `File.fnmatch?` - # @param option [Integer] Option flag to control globbing behavior. These - # are constants defined on `File`, such as `File::FNM_DOTMATCH`. - # `File.fnmatch?` and `Dir.glob` only take one option argument, if you - # need to combine options, you must `|` the constants together. To make - # `File.fnmatch?` behave like `Dir.glob`, `File::FNM_PATHNAME` is - # always enabled. - def select_files_by_glob(pattern, option = 0) - combined_opts = option | File::FNM_PATHNAME - cookbook_settings[:all_files].values.select do |path| - File.fnmatch?(pattern, path, combined_opts) - end - end - def remove_ignored_files cookbook_settings[:all_files].reject! do |relative_path, full_path| chefignore.ignored?(relative_path) diff --git a/lib/chef/cookbook/file_system_file_vendor.rb b/lib/chef/cookbook/file_system_file_vendor.rb index 8088ed00cd..1f43095ea3 100644 --- a/lib/chef/cookbook/file_system_file_vendor.rb +++ b/lib/chef/cookbook/file_system_file_vendor.rb @@ -35,7 +35,7 @@ class Chef attr_reader :repo_paths def initialize(manifest, *repo_paths) - @cookbook_name = manifest[:cookbook_name] + @cookbook_name = manifest.name @repo_paths = repo_paths.flatten raise ArgumentError, "You must specify at least one repo path" if repo_paths.empty? end diff --git a/lib/chef/cookbook/gem_installer.rb b/lib/chef/cookbook/gem_installer.rb index df73ce3ee4..5b1426e4e8 100644 --- a/lib/chef/cookbook/gem_installer.rb +++ b/lib/chef/cookbook/gem_installer.rb @@ -1,5 +1,5 @@ #-- -# Copyright:: Copyright (c) 2010-2016 Chef Software, Inc. +# Copyright:: Copyright (c) 2010-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,10 +36,12 @@ class Chef # Installs the gems into the omnibus gemset. # def install - cookbook_gems = [] + cookbook_gems = Hash.new { |h, k| h[k] = [] } cookbook_collection.each do |cookbook_name, cookbook_version| - cookbook_gems += cookbook_version.metadata.gems + cookbook_version.metadata.gems.each do |args| + cookbook_gems[args.first] += args[1..-1] + end end events.cookbook_gem_start(cookbook_gems) @@ -48,9 +50,11 @@ class Chef begin Dir.mktmpdir("chef-gem-bundle") do |dir| File.open("#{dir}/Gemfile", "w+") do |tf| - tf.puts "source '#{Chef::Config[:rubygems_url]}'" - cookbook_gems.each do |args| - tf.puts "gem(*#{args.inspect})" + Array(Chef::Config[:rubygems_url] || "https://www.rubygems.org").each do |s| + tf.puts "source '#{s}'" + end + cookbook_gems.each do |gem_name, args| + tf.puts "gem(*#{([gem_name] + args).inspect})" end tf.close Chef::Log.debug("generated Gemfile contents:") diff --git a/lib/chef/cookbook/manifest_v0.rb b/lib/chef/cookbook/manifest_v0.rb new file mode 100644 index 0000000000..fd2d62a6d4 --- /dev/null +++ b/lib/chef/cookbook/manifest_v0.rb @@ -0,0 +1,68 @@ +# Author:: Daniel DeLeo (<dan@chef.io>) +# Copyright:: Copyright 2015-2016, Chef Software Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require "chef/json_compat" +require "chef/mixin/versioned_api" + +class Chef + class Cookbook + class ManifestV0 + extend Chef::Mixin::VersionedAPI + + minimum_api_version 0 + + COOKBOOK_SEGMENTS = %w{ resources providers recipes definitions libraries attributes files templates root_files } + + def self.from_hash(hash) + response = Mash.new(hash) + response[:all_files] = COOKBOOK_SEGMENTS.inject([]) do |memo, segment| + next memo if hash[segment].nil? || hash[segment].empty? + hash[segment].each do |file| + file["name"] = "#{segment}/#{file["name"]}" unless segment == "root_files" + memo << file + end + response.delete(segment) + memo + end + response + end + + def self.to_hash(manifest) + result = manifest.manifest.dup + result.delete("all_files") + + files = manifest.by_parent_directory + files.keys.each_with_object(result) do |parent, memo| + if COOKBOOK_SEGMENTS.include?(parent) + memo[parent] ||= [] + files[parent].each do |file| + file["name"] = file["name"].split("/")[1] unless parent == "root_files" + file.delete("full_path") + memo[parent] << file + end + end + end + # Ensure all segments are set to [] if they don't exist. + # See https://github.com/chef/chef/issues/6044 + COOKBOOK_SEGMENTS.each do |segment| + result[segment] ||= [] + end + + result.merge({ "frozen?" => manifest.frozen_version?, "chef_type" => "cookbook_version" }) + end + end + end +end diff --git a/lib/chef/cookbook/manifest_v2.rb b/lib/chef/cookbook/manifest_v2.rb new file mode 100644 index 0000000000..59b5c9afb0 --- /dev/null +++ b/lib/chef/cookbook/manifest_v2.rb @@ -0,0 +1,41 @@ +# Copyright:: Copyright 2015-2016, Chef Software Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require "chef/json_compat" +require "chef/mixin/versioned_api" + +class Chef + class Cookbook + class ManifestV2 + extend Chef::Mixin::VersionedAPI + + minimum_api_version 2 + + def self.from_hash(hash) + Chef::Log.debug "processing manifest: #{hash}" + Mash.new hash + end + + def self.to_hash(manifest) + result = manifest.manifest.dup + result["all_files"].map! { |file| file.delete("full_path"); file } + result["frozen?"] = manifest.frozen_version? + result["chef_type"] = "cookbook_version" + result.to_hash + end + + end + end +end diff --git a/lib/chef/cookbook/metadata.rb b/lib/chef/cookbook/metadata.rb index a8ec901e97..5efadd6f62 100644 --- a/lib/chef/cookbook/metadata.rb +++ b/lib/chef/cookbook/metadata.rb @@ -44,13 +44,8 @@ class Chef LICENSE = "license".freeze PLATFORMS = "platforms".freeze DEPENDENCIES = "dependencies".freeze - RECOMMENDATIONS = "recommendations".freeze - SUGGESTIONS = "suggestions".freeze - CONFLICTING = "conflicting".freeze PROVIDING = "providing".freeze - REPLACING = "replacing".freeze ATTRIBUTES = "attributes".freeze - GROUPINGS = "groupings".freeze RECIPES = "recipes".freeze VERSION = "version".freeze SOURCE_URL = "source_url".freeze @@ -62,17 +57,12 @@ class Chef COMPARISON_FIELDS = [ :name, :description, :long_description, :maintainer, :maintainer_email, :license, :platforms, :dependencies, - :recommendations, :suggestions, :conflicting, :providing, - :replacing, :attributes, :groupings, :recipes, :version, + :providing, :attributes, :recipes, :version, :source_url, :issues_url, :privacy, :chef_versions, :ohai_versions, :gems ] VERSION_CONSTRAINTS = { :depends => DEPENDENCIES, - :recommends => RECOMMENDATIONS, - :suggests => SUGGESTIONS, - :conflicts => CONFLICTING, :provides => PROVIDING, - :replaces => REPLACING, :chef_version => CHEF_VERSIONS, :ohai_version => OHAI_VERSIONS } @@ -81,13 +71,8 @@ class Chef attr_reader :platforms attr_reader :dependencies - attr_reader :recommendations - attr_reader :suggestions - attr_reader :conflicting attr_reader :providing - attr_reader :replacing attr_reader :attributes - attr_reader :groupings attr_reader :recipes attr_reader :version @@ -120,13 +105,8 @@ class Chef @platforms = Mash.new @dependencies = Mash.new - @recommendations = Mash.new - @suggestions = Mash.new - @conflicting = Mash.new @providing = Mash.new - @replacing = Mash.new @attributes = Mash.new - @groupings = Mash.new @recipes = Mash.new @version = Version.new("0.0.0") @source_url = "" @@ -315,57 +295,6 @@ class Chef @dependencies[cookbook] end - # Adds a recommendation for another cookbook, with version checking strings. - # - # === Parameters - # cookbook<String>:: The cookbook - # version<String>:: A version constraint of the form "OP VERSION", - # where OP is one of < <= = > >= ~> and VERSION has - # the form x.y.z or x.y. - # - # === Returns - # versions<Array>:: Returns the list of versions for the platform - def recommends(cookbook, *version_args) - version = new_args_format(:recommends, cookbook, version_args) - constraint = validate_version_constraint(:recommends, cookbook, version) - @recommendations[cookbook] = constraint.to_s - @recommendations[cookbook] - end - - # Adds a suggestion for another cookbook, with version checking strings. - # - # === Parameters - # cookbook<String>:: The cookbook - # version<String>:: A version constraint of the form "OP VERSION", - # where OP is one of < <= = > >= ~> and VERSION has the - # formx.y.z or x.y. - # - # === Returns - # versions<Array>:: Returns the list of versions for the platform - def suggests(cookbook, *version_args) - version = new_args_format(:suggests, cookbook, version_args) - constraint = validate_version_constraint(:suggests, cookbook, version) - @suggestions[cookbook] = constraint.to_s - @suggestions[cookbook] - end - - # Adds a conflict for another cookbook, with version checking strings. - # - # === Parameters - # cookbook<String>:: The cookbook - # version<String>:: A version constraint of the form "OP VERSION", - # where OP is one of < <= = > >= ~> and VERSION has - # the form x.y.z or x.y. - # - # === Returns - # versions<Array>:: Returns the list of versions for the platform - def conflicts(cookbook, *version_args) - version = new_args_format(:conflicts, cookbook, version_args) - constraint = validate_version_constraint(:conflicts, cookbook, version) - @conflicting[cookbook] = constraint.to_s - @conflicting[cookbook] - end - # Adds a recipe, definition, or resource provided by this cookbook. # # Recipes are specified as normal @@ -387,22 +316,6 @@ class Chef @providing[cookbook] end - # Adds a cookbook that is replaced by this one, with version checking strings. - # - # === Parameters - # cookbook<String>:: The cookbook we replace - # version<String>:: A version constraint of the form "OP VERSION", - # where OP is one of < <= = > >= ~> and VERSION has the form x.y.z or x.y. - # - # === Returns - # versions<Array>:: Returns the list of versions for the platform - def replaces(cookbook, *version_args) - version = new_args_format(:replaces, cookbook, version_args) - constraint = validate_version_constraint(:replaces, cookbook, version) - @replacing[cookbook] = constraint.to_s - @replacing[cookbook] - end - # Metadata DSL to set a valid chef_version. May be declared multiple times # with the result being 'OR'd such that if any statements match, the version # is considered supported. Uses Gem::Requirement for its implementation. @@ -519,18 +432,6 @@ class Chef @attributes[name] end - def grouping(name, options) - validate( - options, - { - :title => { :kind_of => String }, - :description => { :kind_of => String }, - } - ) - @groupings[name] = options - @groupings[name] - end - # Convert an Array of Gem::Dependency objects (chef_version/ohai_version) to an Array. # # Gem::Dependencey#to_s is not useful, and there is no #to_json defined on it or its component @@ -575,13 +476,8 @@ class Chef LICENSE => license, PLATFORMS => platforms, DEPENDENCIES => dependencies, - RECOMMENDATIONS => recommendations, - SUGGESTIONS => suggestions, - CONFLICTING => conflicting, PROVIDING => providing, - REPLACING => replacing, ATTRIBUTES => attributes, - GROUPINGS => groupings, RECIPES => recipes, VERSION => version, SOURCE_URL => source_url, @@ -612,13 +508,8 @@ class Chef @license = o[LICENSE] if o.has_key?(LICENSE) @platforms = o[PLATFORMS] if o.has_key?(PLATFORMS) @dependencies = handle_deprecated_constraints(o[DEPENDENCIES]) if o.has_key?(DEPENDENCIES) - @recommendations = handle_deprecated_constraints(o[RECOMMENDATIONS]) if o.has_key?(RECOMMENDATIONS) - @suggestions = handle_deprecated_constraints(o[SUGGESTIONS]) if o.has_key?(SUGGESTIONS) - @conflicting = handle_deprecated_constraints(o[CONFLICTING]) if o.has_key?(CONFLICTING) @providing = o[PROVIDING] if o.has_key?(PROVIDING) - @replacing = handle_deprecated_constraints(o[REPLACING]) if o.has_key?(REPLACING) @attributes = o[ATTRIBUTES] if o.has_key?(ATTRIBUTES) - @groupings = o[GROUPINGS] if o.has_key?(GROUPINGS) @recipes = o[RECIPES] if o.has_key?(RECIPES) @version = o[VERSION] if o.has_key?(VERSION) @source_url = o[SOURCE_URL] if o.has_key?(SOURCE_URL) @@ -726,7 +617,7 @@ class Chef if block_given? super else - Chef::Log.debug "ignoring method #{method} on cookbook with name #{name}, possible typo or future metadata?" + Chef::Log.debug "ignoring method #{method} on cookbook with name #{name}, possible typo or the ghosts of metadata past or future?" end end diff --git a/lib/chef/cookbook/remote_file_vendor.rb b/lib/chef/cookbook/remote_file_vendor.rb index e63d094dc4..cfd7789311 100644 --- a/lib/chef/cookbook/remote_file_vendor.rb +++ b/lib/chef/cookbook/remote_file_vendor.rb @@ -30,7 +30,7 @@ class Chef def initialize(manifest, rest) @manifest = manifest - @cookbook_name = @manifest[:cookbook_name] || @manifest[:name] + @cookbook_name = @manifest.name @rest = rest end @@ -44,8 +44,8 @@ class Chef raise "get_filename: Cannot determine segment/filename for incoming filename #{filename}" end - raise "No such segment #{segment} in cookbook #{@cookbook_name}" unless @manifest[segment] - found_manifest_record = @manifest[segment].find { |manifest_record| manifest_record[:path] == filename } + raise "No such segment #{segment} in cookbook #{@cookbook_name}" unless @manifest.files_for(segment) + found_manifest_record = @manifest.files_for(segment).find { |manifest_record| manifest_record[:path] == filename } raise "No such file #{filename} in #{@cookbook_name}" unless found_manifest_record cache_filename = File.join("cookbooks", @cookbook_name, found_manifest_record["path"]) diff --git a/lib/chef/cookbook/synchronizer.rb b/lib/chef/cookbook/synchronizer.rb index bb44bc3d5c..625f3b4f20 100644 --- a/lib/chef/cookbook/synchronizer.rb +++ b/lib/chef/cookbook/synchronizer.rb @@ -62,18 +62,11 @@ class Chef # Synchronizes the locally cached copies of cookbooks with the files on the # server. class CookbookSynchronizer - CookbookFile = Struct.new(:cookbook, :segment, :manifest_record) + CookbookFile = Struct.new(:cookbook, :manifest_record) attr_accessor :remove_obsoleted_files def initialize(cookbooks_by_name, events) - @eager_segments = Chef::CookbookVersion::COOKBOOK_SEGMENTS.dup - unless Chef::Config[:no_lazy_load] - @eager_segments.delete(:files) - @eager_segments.delete(:templates) - end - @eager_segments.freeze - @cookbooks_by_name, @events = cookbooks_by_name, events @cookbook_full_file_paths = {} @@ -101,15 +94,19 @@ class Chef end def cookbook_segment(cookbook_name, segment) - @cookbooks_by_name[cookbook_name].manifest[segment] + @cookbooks_by_name[cookbook_name].files_for(segment) end def files + exclude = unless Chef::Config[:no_lazy_load] + [ :files, :templates ] + else + [] + end + @files ||= cookbooks.inject([]) do |memo, cookbook| - @eager_segments.each do |segment| - cookbook.manifest[segment].each do |manifest_record| - memo << CookbookFile.new(cookbook, segment, manifest_record) - end + cookbook.each_file(excluded_parts: exclude) do |manifest_record| + memo << CookbookFile.new(cookbook, manifest_record) end memo end @@ -162,8 +159,10 @@ class Chef @events.cookbook_sync_start(cookbook_count) queue.process(Chef::Config[:cookbook_sync_threads]) + # Ensure that cookbooks know where they're rooted at, for manifest purposes. + ensure_cookbook_paths # Update the full file paths in the manifest - update_cookbook_filenames() + update_cookbook_filenames rescue Exception => e @events.cookbook_sync_failed(cookbooks, e) @@ -176,9 +175,8 @@ class Chef # Saves the full_path to the file of the cookbook to be updated # in the manifest later def save_full_file_path(file, full_path) - @cookbook_full_file_paths[file.cookbook] ||= {} - @cookbook_full_file_paths[file.cookbook][file.segment] ||= [ ] - @cookbook_full_file_paths[file.cookbook][file.segment] << full_path + @cookbook_full_file_paths[file.cookbook] ||= [] + @cookbook_full_file_paths[file.cookbook] << full_path end # remove cookbooks that are not referenced in the expanded run_list at all @@ -229,10 +227,15 @@ class Chef end def update_cookbook_filenames - @cookbook_full_file_paths.each do |cookbook, file_segments| - file_segments.each do |segment, full_paths| - cookbook.replace_segment_filenames(segment, full_paths) - end + @cookbook_full_file_paths.each do |cookbook, full_paths| + cookbook.all_files = full_paths + end + end + + def ensure_cookbook_paths + cookbooks.each do |cookbook| + cb_dir = File.join(Chef::Config[:file_cache_path], "cookbooks", cookbook.name) + cookbook.root_paths = Array(cb_dir) end end diff --git a/lib/chef/cookbook_manifest.rb b/lib/chef/cookbook_manifest.rb index d6de9dd029..125f353d21 100644 --- a/lib/chef/cookbook_manifest.rb +++ b/lib/chef/cookbook_manifest.rb @@ -15,7 +15,10 @@ # limitations under the License. require "forwardable" +require "chef/mixin/versioned_api" require "chef/util/path_helper" +require "chef/cookbook/manifest_v0" +require "chef/cookbook/manifest_v2" require "chef/log" class Chef @@ -24,17 +27,11 @@ class Chef # to a Chef Server. class CookbookManifest - # Duplicates the same constant in CookbookVersion. We cannot remove it - # there because it is treated by other code as part of CookbookVersion's - # public API (also used in some deprecated methods). - COOKBOOK_SEGMENTS = [ :resources, :providers, :recipes, :definitions, :libraries, :attributes, :files, :templates, :root_files ].freeze - extend Forwardable attr_reader :cookbook_version def_delegator :@cookbook_version, :root_paths - def_delegator :@cookbook_version, :segment_filenames def_delegator :@cookbook_version, :name def_delegator :@cookbook_version, :identifier def_delegator :@cookbook_version, :metadata @@ -56,10 +53,7 @@ class Chef # the format used by the `cookbook_artifacts` endpoint (for policyfiles). # Setting this option also changes the behavior of #save_url and # #force_save_url such that CookbookVersions will be uploaded to the new - # `cookbook_artifacts` API. This endpoint is currently under active - # development and the format is expected to change frequently, therefore - # the result of #manifest, #to_hash, and #to_json will not be stable when - # `policy_mode` is enabled. + # `cookbook_artifacts` API. def initialize(cookbook_version, policy_mode: false) @cookbook_version = cookbook_version @policy_mode = !!policy_mode @@ -126,10 +120,7 @@ class Chef end def to_hash - result = manifest.dup - result["frozen?"] = frozen_version? - result["chef_type"] = "cookbook_version" - result.to_hash + CookbookManifestVersions.to_hash(self) end def to_json(*a) @@ -164,15 +155,48 @@ class Chef # make the corresponding changes to the cookbook_version object. Required # to provide backward compatibility with CookbookVersion#manifest= method. def update_from(new_manifest) - @manifest = Mash.new new_manifest + @manifest = Chef::CookbookManifestVersions.from_hash(new_manifest) @checksums = extract_checksums_from_manifest(@manifest) @manifest_records_by_path = extract_manifest_records_by_path(@manifest) + end + + def files_for(part) + return root_files if part.to_s == "root_files" + manifest[:all_files].select do |file| + seg = file[:name].split("/")[0] + part.to_s == seg + end + end + + def each_file(excluded_parts: [], &block) + excluded_parts = Array(excluded_parts).map { |p| p.to_s } - COOKBOOK_SEGMENTS.each do |segment| - next unless @manifest.has_key?(segment) - filenames = @manifest[segment].map { |manifest_record| manifest_record["name"] } + manifest[:all_files].each do |file| + seg = file[:name].split("/")[0] + next if excluded_parts.include?(seg) + yield file if block_given? + end + end + + def by_parent_directory + @by_parent_directory ||= + manifest[:all_files].inject({}) do |memo, file| + parts = file[:name].split("/") + parent = if parts.length == 1 + "root_files" + else + parts[0] + end + + memo[parent] ||= [] + memo[parent] << file + memo + end + end - cookbook_version.replace_segment_filenames(segment, filenames) + def root_files + manifest[:all_files].select do |file| + file[:name].split("/").length == 1 end end @@ -186,15 +210,7 @@ class Chef # See #preferred_manifest_record for a description an individual manifest record. def generate_manifest manifest = Mash.new({ - :recipes => Array.new, - :definitions => Array.new, - :libraries => Array.new, - :attributes => Array.new, - :files => Array.new, - :templates => Array.new, - :resources => Array.new, - :providers => Array.new, - :root_files => Array.new, + all_files: Array.new, }) @checksums = {} @@ -203,24 +219,24 @@ class Chef raise "Cookbook #{name} does not have root_paths! Cannot generate manifest." end - COOKBOOK_SEGMENTS.each do |segment| - segment_filenames(segment).each do |segment_file| - next if File.directory?(segment_file) + @cookbook_version.all_files.each do |file| + next if File.directory?(file) - path, specificity = parse_segment_file_from_root_paths(segment, segment_file) - file_name = File.basename(path) + name, path, specificity = parse_file_from_root_paths(file) - csum = checksum_cookbook_file(segment_file) - @checksums[csum] = segment_file - rs = Mash.new({ - :name => file_name, - :path => path, - :checksum => csum, - :specificity => specificity, - }) + csum = checksum_cookbook_file(file) + @checksums[csum] = file + rs = Mash.new({ + :name => name, + :path => path, + :checksum => csum, + :specificity => specificity, + # full_path is not a part of the normal manifest, but is very useful to keep around. + # uploaders should strip this out. + :full_path => file, + }) - manifest[segment] << rs - end + manifest[:all_files] << rs end manifest[:metadata] = metadata @@ -238,38 +254,42 @@ class Chef @manifest = manifest end - def parse_segment_file_from_root_paths(segment, segment_file) + def parse_file_from_root_paths(file) root_paths.each do |root_path| - pathname = Chef::Util::PathHelper.relative_path_from(root_path, segment_file) + pathname = Chef::Util::PathHelper.relative_path_from(root_path, file) parts = pathname.each_filename.take(2) # Check if path is actually under root_path next if parts[0] == ".." - if segment == :templates || segment == :files + + # if we have a root_file, such as metadata.rb, the first part will be "." + return [ pathname.to_s, pathname.to_s, "default" ] if parts.length == 1 + + segment = parts[0] + + name = File.join(segment, pathname.basename.to_s) + + if segment == "templates" || segment == "files" # Check if pathname looks like files/foo or templates/foo (unscoped) if pathname.each_filename.to_a.length == 2 # Use root_default in case the same path exists at root_default and default - return [ pathname.to_s, "root_default" ] + return [ name, pathname.to_s, "root_default" ] else - return [ pathname.to_s, parts[1] ] + return [ name, pathname.to_s, parts[1] ] end else - return [ pathname.to_s, "default" ] + return [ name, pathname.to_s, "default" ] end end - Chef::Log.error("Cookbook file #{segment_file} not under cookbook root paths #{root_paths.inspect}.") - raise "Cookbook file #{segment_file} not under cookbook root paths #{root_paths.inspect}." + Chef::Log.error("Cookbook file #{file} not under cookbook root paths #{root_paths.inspect}.") + raise "Cookbook file #{file} not under cookbook root paths #{root_paths.inspect}." end def extract_checksums_from_manifest(manifest) - checksums = {} - COOKBOOK_SEGMENTS.each do |segment| - next unless manifest.has_key?(segment) - manifest[segment].each do |manifest_record| - checksums[manifest_record[:checksum]] = nil - end + manifest[:all_files].inject({}) do |memo, manifest_record| + memo[manifest_record[:checksum]] = nil + memo end - checksums end def checksum_cookbook_file(filepath) @@ -277,14 +297,20 @@ class Chef end def extract_manifest_records_by_path(manifest) - manifest_records_by_path = {} - COOKBOOK_SEGMENTS.each do |segment| - next unless manifest.has_key?(segment) - manifest[segment].each do |manifest_record| - manifest_records_by_path[manifest_record[:path]] = manifest_record - end + manifest[:all_files].inject({}) do |memo, manifest_record| + memo[manifest_record[:path]] = manifest_record + memo end - manifest_records_by_path end + + end + class CookbookManifestVersions + + extend Chef::Mixin::VersionedAPIFactory + add_versioned_api_class Chef::Cookbook::ManifestV0 + add_versioned_api_class Chef::Cookbook::ManifestV2 + + def_versioned_delegator :from_hash + def_versioned_delegator :to_hash end end diff --git a/lib/chef/cookbook_site_streaming_uploader.rb b/lib/chef/cookbook_site_streaming_uploader.rb index c0e85ff984..1641992eac 100644 --- a/lib/chef/cookbook_site_streaming_uploader.rb +++ b/lib/chef/cookbook_site_streaming_uploader.rb @@ -43,15 +43,13 @@ class Chef FileUtils.mkdir_p(tmp_cookbook_dir) Chef::Log.debug("Staging at #{tmp_cookbook_dir}") checksums_to_on_disk_paths = cookbook.checksums - Chef::CookbookVersion::COOKBOOK_SEGMENTS.each do |segment| - cookbook.manifest[segment].each do |manifest_record| - path_in_cookbook = manifest_record[:path] - on_disk_path = checksums_to_on_disk_paths[manifest_record[:checksum]] - dest = File.join(tmp_cookbook_dir, cookbook.name.to_s, path_in_cookbook) - FileUtils.mkdir_p(File.dirname(dest)) - Chef::Log.debug("Staging #{on_disk_path} to #{dest}") - FileUtils.cp(on_disk_path, dest) - end + cookbook.each_file do |manifest_record| + path_in_cookbook = manifest_record[:path] + on_disk_path = checksums_to_on_disk_paths[manifest_record[:checksum]] + dest = File.join(tmp_cookbook_dir, cookbook.name.to_s, path_in_cookbook) + FileUtils.mkdir_p(File.dirname(dest)) + Chef::Log.debug("Staging #{on_disk_path} to #{dest}") + FileUtils.cp(on_disk_path, dest) end # First, generate metadata diff --git a/lib/chef/cookbook_uploader.rb b/lib/chef/cookbook_uploader.rb index bb75234563..5e11314190 100644 --- a/lib/chef/cookbook_uploader.rb +++ b/lib/chef/cookbook_uploader.rb @@ -40,7 +40,7 @@ class Chef def initialize(cookbooks, opts = {}) @opts = opts @cookbooks = Array(cookbooks) - @rest = opts[:rest] || Chef::ServerAPI.new(Chef::Config[:chef_server_url]) + @rest = opts[:rest] || Chef::ServerAPI.new(Chef::Config[:chef_server_url], version_class: Chef::CookbookManifestVersions) @concurrency = opts[:concurrency] || 10 @policy_mode = opts[:policy_mode] || false end diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb index 7353941bb1..dcb8c97548 100644 --- a/lib/chef/cookbook_version.rb +++ b/lib/chef/cookbook_version.rb @@ -37,45 +37,23 @@ class Chef class CookbookVersion include Comparable + extend Forwardable + + def_delegator :@cookbook_manifest, :files_for + def_delegator :@cookbook_manifest, :each_file COOKBOOK_SEGMENTS = [ :resources, :providers, :recipes, :definitions, :libraries, :attributes, :files, :templates, :root_files ] - attr_accessor :all_files + attr_reader :all_files attr_accessor :root_paths - attr_accessor :definition_filenames - attr_accessor :template_filenames - attr_accessor :file_filenames - attr_accessor :library_filenames - attr_accessor :resource_filenames - attr_accessor :provider_filenames - attr_accessor :root_filenames attr_accessor :name - attr_accessor :metadata_filenames - - def status=(new_status) - Chef.deprecated(:internal_api, "Deprecated method `status' called. This method will be removed.") - @status = new_status - end - - def status - Chef.deprecated(:internal_api, "Deprecated method `status' called. This method will be removed.") - @status - end # A Chef::Cookbook::Metadata object. It has a setter that fixes up the # metadata to add descriptions of the recipes contained in this # CookbookVersion. attr_reader :metadata - # attribute_filenames also has a setter that has non-default - # functionality. - attr_reader :attribute_filenames - - # recipe_filenames also has a setter that has non-default - # functionality. - attr_reader :recipe_filenames - attr_reader :recipe_filenames_by_name attr_reader :attribute_filenames_by_short_filename @@ -96,6 +74,11 @@ class Chef root_paths[0] end + def all_files=(files) + @all_files = Array(files) + cookbook_manifest.reset! + end + # This is the one and only method that knows how cookbook files' # checksums are generated. def self.checksum_cookbook_file(filepath) @@ -118,23 +101,10 @@ class Chef @root_paths = root_paths @frozen = false - @attribute_filenames = Array.new - @definition_filenames = Array.new - @template_filenames = Array.new - @file_filenames = Array.new - @recipe_filenames = Array.new - @recipe_filenames_by_name = Hash.new - @library_filenames = Array.new - @resource_filenames = Array.new - @provider_filenames = Array.new - @metadata_filenames = Array.new - @root_filenames = Array.new - @all_files = Array.new - # deprecated - @status = :ready @file_vendor = nil + @cookbook_manifest = Chef::CookbookManifest.new(self) @metadata = Chef::Cookbook::Metadata.new @chef_server_rest = chef_server_rest end @@ -163,10 +133,25 @@ class Chef "#{name}-#{version}" end - def attribute_filenames=(*filenames) - @attribute_filenames = filenames.flatten - @attribute_filenames_by_short_filename = filenames_by_name(attribute_filenames) - attribute_filenames + def attribute_filenames_by_short_filename + @attribute_filenames_by_short_filename ||= begin + name_map = filenames_by_name(files_for("attributes")) + root_alias = cookbook_manifest.root_files.find { |record| record[:name] == "attributes.rb" } + name_map["default"] = root_alias[:full_path] if root_alias + name_map + end + end + + def recipe_filenames_by_name + @recipe_filenames_by_name ||= begin + name_map = filenames_by_name(files_for("recipes")) + root_alias = cookbook_manifest.root_files.find { |record| record[:name] == "recipe.rb" } + if root_alias + Chef::Log.error("Cookbook #{name} contains both recipe.rb and and recipes/default.rb, ignoring recipes/default.rb") if name_map["default"] + name_map["default"] = root_alias[:full_path] + end + name_map + end end def metadata=(metadata) @@ -175,14 +160,14 @@ class Chef @metadata end - ## BACKCOMPAT/DEPRECATED - Remove these and fix breakage before release [DAN - 5/20/2010]## - alias :attribute_files :attribute_filenames - alias :attribute_files= :attribute_filenames= - def manifest cookbook_manifest.manifest end + def manifest=(new_manifest) + cookbook_manifest.update_from(new_manifest) + end + # Returns a hash of checksums to either nil or the on disk path (which is # done by generate_manifest). def checksums @@ -193,29 +178,16 @@ class Chef cookbook_manifest.manifest_records_by_path end - def manifest=(new_manifest) - cookbook_manifest.update_from(new_manifest) - end - # Return recipe names in the form of cookbook_name::recipe_name def fully_qualified_recipe_names - results = Array.new - recipe_filenames_by_name.each_key do |rname| - results << "#{name}::#{rname}" + files_for("recipes").inject([]) do |memo, recipe| + rname = recipe[:name].split("/")[1] + rname = File.basename(rname, ".rb") + memo << "#{name}::#{rname}" + memo end - results - end - - def recipe_filenames=(*filenames) - @recipe_filenames = filenames.flatten - @recipe_filenames_by_name = filenames_by_name(recipe_filenames) - recipe_filenames end - ## BACKCOMPAT/DEPRECATED - Remove these and fix breakage before release [DAN - 5/20/2010]## - alias :recipe_files :recipe_filenames - alias :recipe_files= :recipe_filenames= - # called from DSL def load_recipe(recipe_name, run_context) unless recipe_filenames_by_name.has_key?(recipe_name) @@ -235,41 +207,7 @@ class Chef end def segment_filenames(segment) - unless COOKBOOK_SEGMENTS.include?(segment) - raise ArgumentError, "invalid segment #{segment}: must be one of #{COOKBOOK_SEGMENTS.join(', ')}" - end - - case segment.to_sym - when :resources - @resource_filenames - when :providers - @provider_filenames - when :recipes - @recipe_filenames - when :libraries - @library_filenames - when :definitions - @definition_filenames - when :attributes - @attribute_filenames - when :files - @file_filenames - when :templates - @template_filenames - when :root_files - @root_filenames - end - end - - def replace_segment_filenames(segment, filenames) - case segment.to_sym - when :recipes - self.recipe_filenames = filenames - when :attributes - self.attribute_filenames = filenames - else - segment_filenames(segment).replace(filenames) - end + files_for(segment).map { |f| f["full_path"] || File.join(root_dir, f["path"]) } end # Query whether a template file +template_filename+ is available. File @@ -349,7 +287,7 @@ class Chef filenames_by_pref = Hash.new preferences.each { |pref| filenames_by_pref[pref] = Array.new } - manifest[segment].each do |manifest_record| + files_for(segment).each do |manifest_record| manifest_record_path = manifest_record[:path] # find the NON SPECIFIC filenames, but prefer them by filespecificity. @@ -389,7 +327,7 @@ class Chef records_by_pref = Hash.new preferences.each { |pref| records_by_pref[pref] = Array.new } - manifest[segment].each do |manifest_record| + files_for(segment).each do |manifest_record| manifest_record_path = manifest_record[:path] # extract the preference part from the path. @@ -469,12 +407,22 @@ class Chef end private :preferences_for_path + def display + output = Mash.new + output["cookbook_name"] = name + output["name"] = full_name + output["frozen?"] = frozen_version? + output["metadata"] = metadata.to_hash + output["version"] = version + output.merge(cookbook_manifest.by_parent_directory) + end + def self.from_hash(o) cookbook_version = new(o["cookbook_name"] || o["name"]) # We want the Chef::Cookbook::Metadata object to always be inflated - cookbook_version.metadata = Chef::Cookbook::Metadata.from_hash(o["metadata"]) cookbook_version.manifest = o + cookbook_version.metadata = Chef::Cookbook::Metadata.from_hash(o["metadata"]) cookbook_version.identifier = o["identifier"] if o.key?("identifier") # We don't need the following step when we decide to stop supporting deprecated operators in the metadata (e.g. <<, >>) @@ -488,33 +436,6 @@ class Chef from_hash(o) end - # @deprecated This method was used by the Ruby Chef Server and is no longer - # needed. There is no replacement. - def generate_manifest_with_urls - Chef.deprecated(:internal_api, "Deprecated method #generate_manifest_with_urls.") - - rendered_manifest = manifest.dup - COOKBOOK_SEGMENTS.each do |segment| - if rendered_manifest.has_key?(segment) - rendered_manifest[segment].each do |manifest_record| - url_options = { :cookbook_name => name.to_s, :cookbook_version => version, :checksum => manifest_record["checksum"] } - manifest_record["url"] = yield(url_options) - end - end - end - rendered_manifest - end - - def to_hash - # TODO: this should become deprecated when the API for CookbookManifest becomes stable - cookbook_manifest.to_hash - end - - def to_json(*a) - # TODO: this should become deprecated when the API for CookbookManifest becomes stable - cookbook_manifest.to_json - end - def metadata_json_file File.join(root_paths[0], "metadata.json") end @@ -533,22 +454,12 @@ class Chef # REST API ## - def save_url - # TODO: this should become deprecated when the API for CookbookManifest becomes stable - cookbook_manifest.save_url - end - - def force_save_url - # TODO: this should become deprecated when the API for CookbookManifest becomes stable - cookbook_manifest.force_save_url - end - def chef_server_rest @chef_server_rest ||= chef_server_rest end def self.chef_server_rest - Chef::ServerAPI.new(Chef::Config[:chef_server_url]) + Chef::ServerAPI.new(Chef::Config[:chef_server_url], { version_class: Chef::CookbookManifestVersions }) end def destroy @@ -602,12 +513,12 @@ class Chef Chef::Version.new(version) <=> Chef::Version.new(other.version) end - private - def cookbook_manifest @cookbook_manifest ||= CookbookManifest.new(self) end + private + def find_preferred_manifest_record(node, segment, filename) preferences = preferences_for_path(node, segment, filename) @@ -615,15 +526,21 @@ class Chef preferences.find { |preferred_filename| manifest_records_by_path[preferred_filename] } end - # For each filename, produce a mapping of base filename (i.e. recipe name + # For each manifest record, produce a mapping of base filename (i.e. recipe name + # or attribute file) to on disk location + def relative_paths_by_name(records) + records.select { |record| record[:name] =~ /\.rb$/ }.inject({}) { |memo, record| memo[File.basename(record[:name], ".rb")] = record[:path]; memo } + end + + # For each manifest record, produce a mapping of base filename (i.e. recipe name # or attribute file) to on disk location - def filenames_by_name(filenames) - filenames.select { |filename| filename =~ /\.rb$/ }.inject({}) { |memo, filename| memo[File.basename(filename, ".rb")] = filename; memo } + def filenames_by_name(records) + records.select { |record| record[:name] =~ /\.rb$/ }.inject({}) { |memo, record| memo[File.basename(record[:name], ".rb")] = record[:full_path]; memo } end def file_vendor unless @file_vendor - @file_vendor = Chef::Cookbook::FileVendor.create_from_manifest(manifest) + @file_vendor = Chef::Cookbook::FileVendor.create_from_manifest(cookbook_manifest) end @file_vendor end diff --git a/lib/chef/data_bag_item.rb b/lib/chef/data_bag_item.rb index ddb520dc0b..d0fca26125 100644 --- a/lib/chef/data_bag_item.rb +++ b/lib/chef/data_bag_item.rb @@ -75,6 +75,7 @@ class Chef end def raw_data=(new_data) + new_data = Mash.new(new_data) unless new_data.respond_to?(:[]) && new_data.respond_to?(:keys) raise Exceptions::ValidationFailed, "Data Bag Items must contain a Hash or Mash!" end @@ -132,7 +133,7 @@ class Chef item = new item.data_bag(h.delete("data_bag")) if h.key?("data_bag") if h.key?("raw_data") - item.raw_data = Mash.new(h["raw_data"]) + item.raw_data = h["raw_data"] else item.raw_data = h end diff --git a/lib/chef/data_collector/messages.rb b/lib/chef/data_collector/messages.rb index c0683534a9..ca854957d1 100644 --- a/lib/chef/data_collector/messages.rb +++ b/lib/chef/data_collector/messages.rb @@ -73,6 +73,8 @@ class Chef "resources" => reporter_data[:resources].map(&:report_data), "run_id" => run_status.run_id, "run_list" => run_status.node.run_list.for_json, + "policy_name" => run_status.node.policy_name, + "policy_group" => run_status.node.policy_group, "start_time" => run_status.start_time.utc.iso8601, "end_time" => run_status.end_time.utc.iso8601, "source" => collector_source, diff --git a/lib/chef/decorator/unchain.rb b/lib/chef/decorator/unchain.rb index b1e1f9fce1..30179d4e63 100644 --- a/lib/chef/decorator/unchain.rb +++ b/lib/chef/decorator/unchain.rb @@ -38,22 +38,6 @@ class Chef __path__.push(key) @delegate_sd_obj.public_send(__method__, *__path__, value) end - - # unfortunately we have to support method_missing for node.set_unless.foo.bar = 'baz' notation - def method_missing(symbol, *args) - if symbol == :to_ary - merged_attributes.send(symbol, *args) - elsif args.empty? - Chef.deprecated :attributes, %q{method access to node attributes (node.foo.bar) is deprecated and will be removed in Chef 13, please use bracket syntax (node["foo"]["bar"])} - self[symbol] - elsif symbol.to_s =~ /=$/ - Chef.deprecated :attributes, %q{method setting of node attributes (node.foo="bar") is deprecated and will be removed in Chef 13, please use bracket syntax (node["foo"]="bar")} - key_to_set = symbol.to_s[/^(.+)=$/, 1] - self[key_to_set] = (args.length == 1 ? args[0] : args) - else - raise NoMethodError, "Undefined node attribute or method `#{symbol}' on `node'" - end - end end end end diff --git a/lib/chef/deprecated.rb b/lib/chef/deprecated.rb index 461f65225b..04ecfe5a6e 100644 --- a/lib/chef/deprecated.rb +++ b/lib/chef/deprecated.rb @@ -218,6 +218,26 @@ class Chef end end + class MultiresourceMatch < Base + def id + 16 + end + + def target + "multiresource_match.html" + end + end + + class UseInlineResources < Base + def id + 17 + end + + def target + "use_inline_resources.html" + end + end + # id 3694 was deleted class Generic < Base diff --git a/lib/chef/dsl/declare_resource.rb b/lib/chef/dsl/declare_resource.rb index ac3776c92f..6869e77eca 100644 --- a/lib/chef/dsl/declare_resource.rb +++ b/lib/chef/dsl/declare_resource.rb @@ -241,7 +241,7 @@ class Chef resource = build_resource(type, name, created_at: created_at, &resource_attrs_block) - run_context.resource_collection.insert(resource, resource_type: type, instance_name: name) + run_context.resource_collection.insert(resource, resource_type: resource.declared_type, instance_name: resource.name) resource end diff --git a/lib/chef/event_dispatch/base.rb b/lib/chef/event_dispatch/base.rb index 926bbe24b5..0f8013f114 100644 --- a/lib/chef/event_dispatch/base.rb +++ b/lib/chef/event_dispatch/base.rb @@ -195,6 +195,22 @@ class Chef def lwrp_load_complete end + # Called when an ohai plugin file loading starts + def ohai_plugin_load_start(file_count) + end + + # Called when an ohai plugin file has been loaded + def ohai_plugin_file_loaded(path) + end + + # Called when an ohai plugin file has an error on load. + def ohai_plugin_file_load_failed(path, exception) + end + + # Called when an ohai plugin file loading has finished + def ohai_plugin_load_complete + end + # Called before attribute files are loaded def attribute_load_start(attribute_file_count) end diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb index 78bdf0cf4a..a09a3a062c 100644 --- a/lib/chef/exceptions.rb +++ b/lib/chef/exceptions.rb @@ -59,14 +59,6 @@ class Chef class UnsupportedAction < RuntimeError; end class MissingLibrary < RuntimeError; end - class DeprecatedExitCode < RuntimeError - def initalize - super "Exiting with a non RFC 062 Exit Code." - require "chef/application/exit_code" - Chef::Application::ExitCode.notify_deprecated_exit_code - end - end - class CannotDetermineNodeName < RuntimeError def initialize super "Unable to determine node name: configure node_name or configure the system's hostname and fqdn" diff --git a/lib/chef/formatters/error_description.rb b/lib/chef/formatters/error_description.rb index ece33bdd49..ceb2f68539 100644 --- a/lib/chef/formatters/error_description.rb +++ b/lib/chef/formatters/error_description.rb @@ -17,6 +17,8 @@ # limitations under the License. # +require "chef/version" + class Chef module Formatters # == Formatters::ErrorDescription @@ -45,7 +47,7 @@ class Chef display_section(heading, text, out) end end - display_section("Platform:", RUBY_PLATFORM, out) + display_section("System Info:", error_context_info, out) end def for_json @@ -64,6 +66,21 @@ class Chef out.puts "\n" end + def error_context_info + context_info = { chef_version: Chef::VERSION } + if Chef.node + context_info[:platform] = Chef.node["platform"] + context_info[:platform_version] = Chef.node["platform_version"] + end + # A string like "ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15]" + context_info[:ruby] = RUBY_DESCRIPTION + # The argv[0] value. + context_info[:program_name] = $PROGRAM_NAME + # This is kind of wonky but it's the only way to get the entry path script. + context_info[:executable] = File.realpath(caller.last[/^(.*):\d+:in /, 1]) + context_info.map { |k, v| "#{k}=#{v}" }.join("\n") + end + end end end diff --git a/lib/chef/guard_interpreter/default_guard_interpreter.rb b/lib/chef/guard_interpreter/default_guard_interpreter.rb index 449ca9a316..f93c0e04f0 100644 --- a/lib/chef/guard_interpreter/default_guard_interpreter.rb +++ b/lib/chef/guard_interpreter/default_guard_interpreter.rb @@ -1,6 +1,6 @@ # # Author:: Adam Edwards (<adamed@chef.io>) -# Copyright:: Copyright 2014-2016, Chef Software, Inc. +# Copyright:: Copyright 2014-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,7 +33,9 @@ class Chef public def evaluate - shell_out(@command, @command_opts).status.success? + shell_out_with_systems_locale(@command, @command_opts).status.success? + # Timeout fails command rather than chef-client run, see: + # https://tickets.opscode.com/browse/CHEF-2690 rescue Chef::Exceptions::CommandTimeout Chef::Log.warn "Command '#{@command}' timed out" false diff --git a/lib/chef/http/socketless_chef_zero_client.rb b/lib/chef/http/socketless_chef_zero_client.rb index d2f1f45b1d..296330815a 100644 --- a/lib/chef/http/socketless_chef_zero_client.rb +++ b/lib/chef/http/socketless_chef_zero_client.rb @@ -170,6 +170,7 @@ class Chef "QUERY_STRING" => url.query, "SERVER_PORT" => url.port, "HTTP_HOST" => "localhost:#{url.port}", + "HTTP_X_OPS_SERVER_API_VERSION" => headers["X-Ops-Server-API-Version"], "rack.url_scheme" => "chefzero", "rack.input" => StringIO.new(body_str), } diff --git a/lib/chef/knife.rb b/lib/chef/knife.rb index 47ce85b9e3..9c8b984054 100644 --- a/lib/chef/knife.rb +++ b/lib/chef/knife.rb @@ -1,7 +1,7 @@ # # Author:: Adam Jacob (<adam@chef.io>) # Author:: Christopher Brown (<cb@chef.io>) -# Copyright:: Copyright 2009-2016, Chef Software Inc. +# Copyright:: Copyright 2009-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -265,8 +265,7 @@ class Chef ui.fatal("Cannot find subcommand for: '#{args.join(' ')}'") # Mention rehash when the subcommands cache(plugin_manifest.json) is used - if subcommand_loader.is_a?(Chef::Knife::SubcommandLoader::HashedCommandLoader) || - subcommand_loader.is_a?(Chef::Knife::SubcommandLoader::CustomManifestLoader) + if subcommand_loader.is_a?(Chef::Knife::SubcommandLoader::HashedCommandLoader) ui.info("If this is a recently installed plugin, please run 'knife rehash' to update the subcommands cache.") end diff --git a/lib/chef/knife/configure.rb b/lib/chef/knife/configure.rb index 48007bbee7..967a18de87 100644 --- a/lib/chef/knife/configure.rb +++ b/lib/chef/knife/configure.rb @@ -1,6 +1,6 @@ # # Author:: Adam Jacob (<adam@chef.io>) -# Copyright:: Copyright 2009-2016, Chef Software Inc. +# Copyright:: Copyright 2009-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -75,8 +75,6 @@ class Chef ::File.open(config[:config_file], "w") do |f| f.puts <<-EOH -log_level :info -log_location STDOUT node_name '#{new_client_name}' client_key '#{new_client_key}' validation_client_name '#{validation_client_name}' diff --git a/lib/chef/knife/configure_client.rb b/lib/chef/knife/configure_client.rb index 7d0b3d260d..c015687ac7 100644 --- a/lib/chef/knife/configure_client.rb +++ b/lib/chef/knife/configure_client.rb @@ -1,6 +1,6 @@ # # Author:: Adam Jacob (<adam@chef.io>) -# Copyright:: Copyright 2009-2016, Chef Software Inc. +# Copyright:: Copyright 2009-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,8 +34,6 @@ class Chef FileUtils.mkdir_p(@config_dir) ui.info("Writing client.rb") File.open(File.join(@config_dir, "client.rb"), "w") do |file| - file.puts("log_level :info") - file.puts("log_location STDOUT") file.puts("chef_server_url '#{Chef::Config[:chef_server_url]}'") file.puts("validation_client_name '#{Chef::Config[:validation_client_name]}'") end diff --git a/lib/chef/knife/cookbook_create.rb b/lib/chef/knife/cookbook_create.rb index ccb78bb7a6..6122fd52d3 100644 --- a/lib/chef/knife/cookbook_create.rb +++ b/lib/chef/knife/cookbook_create.rb @@ -21,442 +21,9 @@ require "chef/knife" class Chef class Knife class CookbookCreate < Knife - - deps do - require "chef/json_compat" - require "uri" - require "fileutils" - end - - banner "knife cookbook create COOKBOOK (options)" - - option :cookbook_path, - :short => "-o PATH", - :long => "--cookbook-path PATH", - :description => "The directory where the cookbook will be created" - - option :readme_format, - :short => "-r FORMAT", - :long => "--readme-format FORMAT", - :description => "Format of the README file, supported formats are 'md' (markdown) and 'rdoc' (rdoc)" - - option :cookbook_license, - :short => "-I LICENSE", - :long => "--license LICENSE", - :description => "License for cookbook, apachev2, gplv2, gplv3, mit or none" - - option :cookbook_copyright, - :short => "-C COPYRIGHT", - :long => "--copyright COPYRIGHT", - :description => "Name of copyright holder" - - option :cookbook_email, - :short => "-m EMAIL", - :long => "--email EMAIL", - :description => "Email address of cookbook maintainer" - def run - Chef::Log.deprecation <<EOF -This command is being deprecated in favor of `chef generate cookbook` and will soon return an error. -Please use `chef generate cookbook` instead of this command. -EOF - self.config = Chef::Config.merge!(config) - if @name_args.length < 1 - show_usage - ui.fatal("You must specify a cookbook name") - exit 1 - end - - if default_cookbook_path_empty? && parameter_empty?(config[:cookbook_path]) - raise ArgumentError, "Default cookbook_path is not specified in the knife.rb config file, and a value to -o is not provided. Nowhere to write the new cookbook to." - end - - cookbook_path = File.expand_path(Array(config[:cookbook_path]).first) - cookbook_name = @name_args.first - copyright = config[:cookbook_copyright] || "YOUR_COMPANY_NAME" - email = config[:cookbook_email] || "YOUR_EMAIL" - license = ((config[:cookbook_license] != "false") && config[:cookbook_license]) || "none" - readme_format = ((config[:readme_format] != "false") && config[:readme_format]) || "md" - create_cookbook(cookbook_path, cookbook_name, copyright, license) - create_readme(cookbook_path, cookbook_name, readme_format) - create_changelog(cookbook_path, cookbook_name) - create_metadata(cookbook_path, cookbook_name, copyright, email, license, readme_format) - end - - def create_cookbook(dir, cookbook_name, copyright, license) - msg("** Creating cookbook #{cookbook_name} in #{dir}") - FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "attributes")}" - FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "recipes")}" - FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "definitions")}" - FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "libraries")}" - FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "resources")}" - FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "providers")}" - FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "files", "default")}" - FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "templates", "default")}" - unless File.exists?(File.join(dir, cookbook_name, "recipes", "default.rb")) - open(File.join(dir, cookbook_name, "recipes", "default.rb"), "w") do |file| - file.puts <<-EOH -# -# Cookbook Name:: #{cookbook_name} -# Recipe:: default -# -# Copyright #{Time.now.year}, #{copyright} -# -EOH - case license - when "apachev2" - file.puts <<-EOH -# 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. -# -EOH - when "gplv2" - file.puts <<-EOH -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# -EOH - when "gplv3" - file.puts <<-EOH -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -EOH - when "mit" - file.puts <<-EOH -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -EOH - when "none" - file.puts <<-EOH -# All rights reserved - Do Not Redistribute -# -EOH - end - end - end - end - - def create_changelog(dir, cookbook_name) - msg("** Creating CHANGELOG for cookbook: #{cookbook_name}") - unless File.exists?(File.join(dir, cookbook_name, "CHANGELOG.md")) - open(File.join(dir, cookbook_name, "CHANGELOG.md"), "w") do |file| - file.puts <<-EOH -# #{cookbook_name} CHANGELOG - -This file is used to list changes made in each version of the #{cookbook_name} cookbook. - -## 0.1.0 -- [your_name] - Initial release of #{cookbook_name} - -- - - -Check the [Markdown Syntax Guide](http://daringfireball.net/projects/markdown/syntax) for help with Markdown. - -The [Github Flavored Markdown page](http://github.github.com/github-flavored-markdown/) describes the differences between markdown on github and standard markdown. -EOH - end - end + Chef::Log.fatal("knife cookbook create has been removed. Please use `chef generate cookbook` from the ChefDK") end - - def create_readme(dir, cookbook_name, readme_format) - msg("** Creating README for cookbook: #{cookbook_name}") - unless File.exist?(File.join(dir, cookbook_name, "README.#{readme_format}")) - open(File.join(dir, cookbook_name, "README.#{readme_format}"), "w") do |file| - case readme_format - when "rdoc" - file.puts <<-EOH -= #{cookbook_name} Cookbook -TODO: Enter the cookbook description here. - -e.g. -This cookbook makes your favorite breakfast sandwich. - -== Requirements -TODO: List your cookbook requirements. Be sure to include any requirements this cookbook has on platforms, libraries, other cookbooks, packages, operating systems, etc. - -e.g. -==== packages -- +toaster+ - #{cookbook_name} needs toaster to brown your bagel. - -== Attributes -TODO: List your cookbook attributes here. - -e.g. -==== #{cookbook_name}::default -<table> - <tr> - <th>Key</th> - <th>Type</th> - <th>Description</th> - <th>Default</th> - </tr> - <tr> - <td><tt>['#{cookbook_name}']['bacon']</tt></td> - <td>Boolean</td> - <td>whether to include bacon</td> - <td><tt>true</tt></td> - </tr> -</table> - -== Usage -==== #{cookbook_name}::default -TODO: Write usage instructions for each cookbook. - -e.g. -Just include +#{cookbook_name}+ in your node's +run_list+: - - { - "name":"my_node", - "run_list": [ - "recipe[#{cookbook_name}]" - ] - } - -== Contributing -TODO: (optional) If this is a public cookbook, detail the process for contributing. If this is a private cookbook, remove this section. - -e.g. -1. Fork the repository on Github -2. Create a named feature branch (like `add_component_x`) -3. Write your change -4. Write tests for your change (if applicable) -5. Run the tests, ensuring they all pass -6. Submit a Pull Request using Github - -== License and Authors -Authors: TODO: List authors -EOH - when "md", "mkd", "txt" - file.puts <<-EOH -# #{cookbook_name} Cookbook - -TODO: Enter the cookbook description here. - -e.g. -This cookbook makes your favorite breakfast sandwich. - -## Requirements - -TODO: List your cookbook requirements. Be sure to include any requirements this cookbook has on platforms, libraries, other cookbooks, packages, operating systems, etc. - -e.g. -### Platforms - -- SandwichOS - -### Chef - -- Chef 12.0 or later - -### Cookbooks - -- `toaster` - #{cookbook_name} needs toaster to brown your bagel. - -## Attributes - -TODO: List your cookbook attributes here. - -e.g. -### #{cookbook_name}::default - -<table> - <tr> - <th>Key</th> - <th>Type</th> - <th>Description</th> - <th>Default</th> - </tr> - <tr> - <td><tt>['#{cookbook_name}']['bacon']</tt></td> - <td>Boolean</td> - <td>whether to include bacon</td> - <td><tt>true</tt></td> - </tr> -</table> - -## Usage - -### #{cookbook_name}::default - -TODO: Write usage instructions for each cookbook. - -e.g. -Just include `#{cookbook_name}` in your node's `run_list`: - -```json -{ - "name":"my_node", - "run_list": [ - "recipe[#{cookbook_name}]" - ] -} -``` - -## Contributing - -TODO: (optional) If this is a public cookbook, detail the process for contributing. If this is a private cookbook, remove this section. - -e.g. -1. Fork the repository on Github -2. Create a named feature branch (like `add_component_x`) -3. Write your change -4. Write tests for your change (if applicable) -5. Run the tests, ensuring they all pass -6. Submit a Pull Request using Github - -## License and Authors - -Authors: TODO: List authors - -EOH - else - file.puts <<-EOH -#{cookbook_name} Cookbook -#{'=' * "#{cookbook_name} Cookbook".length} - TODO: Enter the cookbook description here. - - e.g. - This cookbook makes your favorite breakfast sandwich. - -Requirements - TODO: List your cookbook requirements. Be sure to include any requirements this cookbook has on platforms, libraries, other cookbooks, packages, operating systems, etc. - - e.g. - toaster #{cookbook_name} needs toaster to brown your bagel. - -Attributes - TODO: List your cookbook attributes here. - - #{cookbook_name} - Key Type Description Default - ['#{cookbook_name}']['bacon'] Boolean whether to include bacon true - -Usage - #{cookbook_name} - TODO: Write usage instructions for each cookbook. - - e.g. - Just include `#{cookbook_name}` in your node's `run_list`: - - [code] - { - "name":"my_node", - "run_list": [ - "recipe[#{cookbook_name}]" - ] - } - [/code] - -Contributing - TODO: (optional) If this is a public cookbook, detail the process for contributing. If this is a private cookbook, remove this section. - - e.g. - 1. Fork the repository on Github - 2. Create a named feature branch (like `add_component_x`) - 3. Write your change - 4. Write tests for your change (if applicable) - 5. Run the tests, ensuring they all pass - 6. Submit a Pull Request using Github - -License and Authors - Authors: TODO: List authors -EOH - end - end - end - end - - def create_metadata(dir, cookbook_name, copyright, email, license, readme_format) - msg("** Creating metadata for cookbook: #{cookbook_name}") - - license_name = case license - when "apachev2" - "Apache 2.0" - when "gplv2" - "GNU Public License 2.0" - when "gplv3" - "GNU Public License 3.0" - when "mit" - "MIT" - when "none" - "All rights reserved" - end - - unless File.exist?(File.join(dir, cookbook_name, "metadata.rb")) - open(File.join(dir, cookbook_name, "metadata.rb"), "w") do |file| - if File.exist?(File.join(dir, cookbook_name, "README.#{readme_format}")) - long_description = "long_description IO.read(File.join(File.dirname(__FILE__), 'README.#{readme_format}'))" - end - file.puts <<-EOH -name '#{cookbook_name}' -maintainer '#{copyright}' -maintainer_email '#{email}' -license '#{license_name}' -description 'Installs/Configures #{cookbook_name}' -#{long_description} -version '0.1.0' -EOH - end - end - end - - private - - def default_cookbook_path_empty? - Chef::Config[:cookbook_path].nil? || Chef::Config[:cookbook_path].empty? - end - - def parameter_empty?(parameter) - parameter.nil? || parameter.empty? - end - end end end diff --git a/lib/chef/knife/cookbook_download.rb b/lib/chef/knife/cookbook_download.rb index 741f444093..b745e77f27 100644 --- a/lib/chef/knife/cookbook_download.rb +++ b/lib/chef/knife/cookbook_download.rb @@ -70,7 +70,7 @@ class Chef ui.info("Downloading #{@cookbook_name} cookbook version #{@version}") cookbook = Chef::CookbookVersion.load(@cookbook_name, @version) - manifest = cookbook.manifest + manifest = cookbook.cookbook_manifest basedir = File.join(config[:download_directory], "#{@cookbook_name}-#{cookbook.version}") if File.exists?(basedir) @@ -83,10 +83,9 @@ class Chef end end - Chef::CookbookVersion::COOKBOOK_SEGMENTS.each do |segment| - next unless manifest.has_key?(segment) + manifest.by_parent_directory.each do |segment, files| ui.info("Downloading #{segment}") - manifest[segment].each do |segment_file| + files.each do |segment_file| dest = File.join(basedir, segment_file["path"].gsub("/", File::SEPARATOR)) Chef::Log.debug("Downloading #{segment_file['path']} to #{dest}") FileUtils.mkdir_p(File.dirname(dest)) diff --git a/lib/chef/knife/cookbook_show.rb b/lib/chef/knife/cookbook_show.rb index d0c930de0a..1d9983632d 100644 --- a/lib/chef/knife/cookbook_show.rb +++ b/lib/chef/knife/cookbook_show.rb @@ -76,9 +76,13 @@ class Chef pretty_print(temp_file.read) when 3 # We are showing a specific part of the cookbook - output(cookbook.manifest[segment]) - when 2 # We are showing the whole cookbook data - output(cookbook) + if segment == "metadata" + output(cookbook.metadata) + else + output(cookbook.files_for(segment)) + end + when 2 # We are showing the whole cookbook + output(cookbook.display) when 1 # We are showing the cookbook versions (all of them) env = config[:environment] api_endpoint = env ? "environments/#{env}/cookbooks/#{cookbook_name}" : "cookbooks/#{cookbook_name}" diff --git a/lib/chef/knife/cookbook_site_vendor.rb b/lib/chef/knife/cookbook_site_vendor.rb deleted file mode 100644 index 291715cc0b..0000000000 --- a/lib/chef/knife/cookbook_site_vendor.rb +++ /dev/null @@ -1,46 +0,0 @@ -# -# Author:: Daniel DeLeo (<dan@chef.io>) -# Copyright:: Copyright 2011-2016, Chef Software Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require "chef/knife" -require "chef/knife/cookbook_site_install" - -class Chef::Knife::CookbookSiteVendor < Chef::Knife::CookbookSiteInstall - - def self.load_deps - superclass.load_deps - end - - def self.options=(new_opts) - superclass.options = new_opts - end - - def self.options - superclass.options - end - - banner(<<-B) -************************************************* -DEPRECATED: please use knife cookbook site install -************************************************* - -#{superclass.banner} -B - - category "deprecated" - -end diff --git a/lib/chef/knife/core/custom_manifest_loader.rb b/lib/chef/knife/core/custom_manifest_loader.rb deleted file mode 100644 index 9fe51599af..0000000000 --- a/lib/chef/knife/core/custom_manifest_loader.rb +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright:: Copyright 2015-2016, Chef Software, Inc -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require "chef/version" -class Chef - class Knife - class SubcommandLoader - - # - # Load a subcommand from a user-supplied - # manifest file - # - class CustomManifestLoader < Chef::Knife::SubcommandLoader - attr_accessor :manifest - def initialize(chef_config_dir, plugin_manifest) - super(chef_config_dir) - @manifest = plugin_manifest - end - - # If the user has created a ~/.chef/plugin_manifest.json file, we'll use - # that instead of inspecting the on-system gems to find the plugins. The - # file format is expected to look like: - # - # { "plugins": { - # "knife-ec2": { - # "paths": [ - # "/home/alice/.rubymanagerthing/gems/knife-ec2-x.y.z/lib/chef/knife/ec2_server_create.rb", - # "/home/alice/.rubymanagerthing/gems/knife-ec2-x.y.z/lib/chef/knife/ec2_server_delete.rb" - # ] - # } - # } - # } - # - # Extraneous content in this file is ignored. This is intentional so that we - # can adapt the file format for potential behavior changes to knife in - # the future. - def find_subcommands_via_manifest - # Format of subcommand_files is "relative_path" (something you can - # Kernel.require()) => full_path. The relative path isn't used - # currently, so we just map full_path => full_path. - subcommand_files = {} - manifest["plugins"].each do |plugin_name, plugin_manifest| - plugin_manifest["paths"].each do |cmd_path| - subcommand_files[cmd_path] = cmd_path - end - end - subcommand_files.merge(find_subcommands_via_dirglob) - end - - def subcommand_files - @subcommand_files ||= (find_subcommands_via_manifest.values + site_subcommands).flatten.uniq - end - end - end - end -end diff --git a/lib/chef/knife/core/subcommand_loader.rb b/lib/chef/knife/core/subcommand_loader.rb index 30a438b780..14b6479b53 100644 --- a/lib/chef/knife/core/subcommand_loader.rb +++ b/lib/chef/knife/core/subcommand_loader.rb @@ -1,6 +1,6 @@ # Author:: Christopher Brown (<cb@chef.io>) # Author:: Daniel DeLeo (<dan@chef.io>) -# Copyright:: Copyright 2009-2016, Chef Software Inc. +# Copyright:: Copyright 2009-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,6 @@ require "chef/version" require "chef/util/path_helper" require "chef/knife/core/gem_glob_loader" require "chef/knife/core/hashed_command_loader" -require "chef/knife/core/custom_manifest_loader" class Chef class Knife @@ -38,7 +37,6 @@ class Chef # class SubcommandLoader attr_reader :chef_config_dir - attr_reader :env # A small factory method. Eventually, this is the only place # where SubcommandLoader should know about its subclasses, but @@ -50,9 +48,6 @@ class Chef if autogenerated_manifest? Chef::Log.debug("Using autogenerated hashed command manifest #{plugin_manifest_path}") Knife::SubcommandLoader::HashedCommandLoader.new(chef_config_dir, plugin_manifest) - elsif custom_manifest? - Chef.deprecated(:internal_api, "Using custom manifest #{plugin_manifest_path} is deprecated. Please use a `knife rehash` autogenerated manifest instead.") - Knife::SubcommandLoader::CustomManifestLoader.new(chef_config_dir, plugin_manifest) else Knife::SubcommandLoader::GemGlobLoader.new(chef_config_dir) end @@ -72,10 +67,6 @@ class Chef plugin_manifest? && plugin_manifest.key?(HashedCommandLoader::KEY) end - def self.custom_manifest? - plugin_manifest? && plugin_manifest.key?("plugins") - end - def self.plugin_manifest Chef::JSONCompat.from_json(File.read(plugin_manifest_path)) end @@ -84,14 +75,8 @@ class Chef Chef::Util::PathHelper.home(".chef", "plugin_manifest.json") end - def initialize(chef_config_dir, env = nil) + def initialize(chef_config_dir) @chef_config_dir = chef_config_dir - - # Deprecated and un-used instance variable. - @env = env - unless env.nil? - Chef.deprecated(:internal_api, "The env argument to Chef::Knife::SubcommandLoader is deprecated. If you are using env to inject/mock HOME, consider mocking Chef::Util::PathHelper.home instead.") - end end # Load all the sub-commands @@ -149,21 +134,6 @@ class Chef end # - # Subclassses should define this themselves. Eventually, this will raise a - # NotImplemented error, but for now, we mimic the behavior the user was likely - # to get in the past. - # - def subcommand_files - Chef.deprecated :internal_api, "Using Chef::Knife::SubcommandLoader directly is deprecated. -Please use Chef::Knife::SubcommandLoader.for_config(chef_config_dir, env)" - @subcommand_files ||= if Chef::Knife::SubcommandLoader.plugin_manifest? - Chef::Knife::SubcommandLoader::CustomManifestLoader.new(chef_config_dir, env).subcommand_files - else - Chef::Knife::SubcommandLoader::GemGlobLoader.new(chef_config_dir, env).subcommand_files - end - end - - # # Utility function for finding an element in a hash given an array # of words and a separator. We find the the longest key in the # hash composed of the given words joined by the separator. diff --git a/lib/chef/knife/search.rb b/lib/chef/knife/search.rb index 7fc76b28c0..c4c3380734 100644 --- a/lib/chef/knife/search.rb +++ b/lib/chef/knife/search.rb @@ -1,6 +1,6 @@ # # Author:: Adam Jacob (<adam@chef.io>) -# Copyright:: Copyright 2009-2016, Chef Software Inc. +# Copyright:: Copyright 2009-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -73,19 +73,18 @@ class Chef def run read_cli_args - fuzzify_query if @type == "node" ui.use_presenter Knife::Core::NodePresenter end q = Chef::Search::Query.new - escaped_query = Addressable::URI.encode_component(@query, Addressable::URI::CharacterClasses::QUERY) result_items = [] result_count = 0 search_args = Hash.new + search_args[:fuzz] = true search_args[:start] = config[:start] if config[:start] search_args[:rows] = config[:rows] if config[:rows] if config[:filter_result] @@ -95,7 +94,7 @@ class Chef end begin - q.search(@type, escaped_query, search_args) do |item| + q.search(@type, @query, search_args) do |item| formatted_item = Hash.new if item.is_a?(Hash) # doing a little magic here to set the correct name @@ -109,7 +108,7 @@ class Chef rescue Net::HTTPServerException => e msg = Chef::JSONCompat.from_json(e.response.body)["error"].first ui.error("knife search failed: #{msg}") - exit 1 + exit 99 end if ui.interchange? @@ -124,6 +123,9 @@ class Chef end end end + + # return a "failure" code to the shell so that knife search can be used in pipes similar to grep + exit 1 if result_count == 0 end def read_cli_args @@ -151,12 +153,6 @@ class Chef end end - def fuzzify_query - if @query !~ /:/ - @query = "tags:*#{@query}* OR roles:*#{@query}* OR fqdn:*#{@query}* OR addresses:*#{@query}* OR policy_name:*#{@query}* OR policy_group:*#{@query}*" - end - end - # This method turns a set of key value pairs in a string into the appropriate data structure that the # chef-server search api is expecting. # expected input is in the form of: diff --git a/lib/chef/knife/ssh.rb b/lib/chef/knife/ssh.rb index d79565991f..380a60fdd6 100644 --- a/lib/chef/knife/ssh.rb +++ b/lib/chef/knife/ssh.rb @@ -1,6 +1,6 @@ # # Author:: Adam Jacob (<adam@chef.io>) -# Copyright:: Copyright 2009-2016, Chef Software Inc. +# Copyright:: Copyright 2009-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -223,7 +223,7 @@ class Chef end @search_count = 0 - query.search(:node, @name_args[0], filter_result: required_attributes) do |item| + query.search(:node, @name_args[0], filter_result: required_attributes, fuzz: true) do |item| @search_count += 1 # we should skip the loop to next iteration if the item # returned by the search is nil @@ -331,18 +331,22 @@ class Chef command = fixup_sudo(command) command.force_encoding("binary") if command.respond_to?(:force_encoding) subsession.open_channel do |chan| - chan.request_pty - chan.exec command do |ch, success| - raise ArgumentError, "Cannot execute #{command}" unless success - ch.on_data do |ichannel, data| - print_data(ichannel[:host], data) - if data =~ /^knife sudo password: / - print_data(ichannel[:host], "\n") - ichannel.send_data("#{get_password}\n") + if config[:on_error] && exit_status != 0 + chan.close() + else + chan.request_pty + chan.exec command do |ch, success| + raise ArgumentError, "Cannot execute #{command}" unless success + ch.on_data do |ichannel, data| + print_data(ichannel[:host], data) + if data =~ /^knife sudo password: / + print_data(ichannel[:host], "\n") + ichannel.send_data("#{get_password}\n") + end + end + ch.on_request "exit-status" do |ichannel, data| + exit_status = [exit_status, data.read_long].max end - end - ch.on_request "exit-status" do |ichannel, data| - exit_status = [exit_status, data.read_long].max end end end diff --git a/lib/chef/mixin/command.rb b/lib/chef/mixin/command.rb deleted file mode 100644 index b1fa7ebd89..0000000000 --- a/lib/chef/mixin/command.rb +++ /dev/null @@ -1,194 +0,0 @@ -# -# Author:: Adam Jacob (<adam@chef.io>) -# Copyright:: Copyright 2008-2016, Chef Software Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require "chef/log" -require "chef/exceptions" -require "tmpdir" -require "fcntl" -require "etc" - -class Chef - module Mixin - - #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - # NOTE: - # The popen4 method upon which all the code here is based has a race - # condition where it may fail to read all of the data written to stdout and - # stderr after the child process exits. The tests for the code here - # occasionally fail because of this race condition, so they have been - # tagged "volatile". - # - # This code is considered deprecated, so it should not need to be modified - # frequently, if at all. HOWEVER, if you do modify the code here, you must - # explicitly enable volatile tests: - # - # bundle exec rspec spec/unit/mixin/command_spec.rb -t volatile - # - # In addition, you should make a note that tests need to be run with - # volatile tests enabled on any pull request or bug report you submit with - # your patch. - #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - module Command - extend self - - # NOTE: run_command is deprecated in favor of using Chef::Shellout which now comes from the mixlib-shellout gem. NOTE # - - if RUBY_PLATFORM =~ /mswin|mingw32|windows/ - require "chef/mixin/command/windows" - include ::Chef::Mixin::Command::Windows - extend ::Chef::Mixin::Command::Windows - else - require "chef/mixin/command/unix" - include ::Chef::Mixin::Command::Unix - extend ::Chef::Mixin::Command::Unix - end - - # === Parameters - # args<Hash>: A number of required and optional arguments - # command<String>, <Array>: A complete command with options to execute or a command and options as an Array - # creates<String>: The absolute path to a file that prevents the command from running if it exists - # cwd<String>: Working directory to execute command in, defaults to Dir.tmpdir - # timeout<String>: How many seconds to wait for the command to execute before timing out - # returns<String>: The single exit value command is expected to return, otherwise causes an exception - # ignore_failure<Boolean>: Whether to raise an exception on failure, or just return the status - # output_on_failure<Boolean>: Return output in raised exception regardless of Log.level - # - # user<String>: The UID or user name of the user to execute the command as - # group<String>: The GID or group name of the group to execute the command as - # environment<Hash>: Pairs of environment variable names and their values to set before execution - # - # === Returns - # Returns the exit status of args[:command] - def run_command(args = {}) - status, stdout, stderr = run_command_and_return_stdout_stderr(args) - - status - end - - # works same as above, except that it returns stdout and stderr - # requirement => platforms like solaris 9,10 has weird issues where - # even in command failure the exit code is zero, so we need to lookup stderr. - def run_command_and_return_stdout_stderr(args = {}) - command_output = "" - - args[:ignore_failure] ||= false - args[:output_on_failure] ||= false - - # TODO: This is the wrong place for this responsibility. - if args.has_key?(:creates) - if File.exists?(args[:creates]) - Chef::Log.debug("Skipping #{args[:command]} - creates #{args[:creates]} exists.") - return false - end - end - - status, stdout, stderr = output_of_command(args[:command], args) - command_output << "STDOUT: #{stdout}" - command_output << "STDERR: #{stderr}" - handle_command_failures(status, command_output, args) - - [status, stdout, stderr] - end - - def output_of_command(command, args) - Chef.deprecated(:run_command, "Chef::Mixin::Command.run_command is deprecated, please use shell_out") - Chef::Log.debug("Executing #{command}") - stderr_string, stdout_string, status = "", "", nil - - exec_processing_block = lambda do |pid, stdin, stdout, stderr| - stdout_string, stderr_string = stdout.string.chomp, stderr.string.chomp - end - - args[:cwd] ||= Dir.tmpdir - unless ::File.directory?(args[:cwd]) - raise Chef::Exceptions::Exec, "#{args[:cwd]} does not exist or is not a directory" - end - - Dir.chdir(args[:cwd]) do - if args[:timeout] - begin - Timeout.timeout(args[:timeout]) do - status = popen4(command, args, &exec_processing_block) - end - rescue Timeout::Error => e - Chef::Log.error("#{command} exceeded timeout #{args[:timeout]}") - raise(e) - end - else - status = popen4(command, args, &exec_processing_block) - end - - Chef::Log.debug("---- Begin output of #{command} ----") - Chef::Log.debug("STDOUT: #{stdout_string}") - Chef::Log.debug("STDERR: #{stderr_string}") - Chef::Log.debug("---- End output of #{command} ----") - Chef::Log.debug("Ran #{command} returned #{status.exitstatus}") - end - - [status, stdout_string, stderr_string] - end - - def handle_command_failures(status, command_output, opts = {}) - return if opts[:ignore_failure] - opts[:returns] ||= 0 - return if Array(opts[:returns]).include?(status.exitstatus) - - # if the log level is not debug, through output of command when we fail - output = "" - if Chef::Log.level == :debug || opts[:output_on_failure] - output << "\n---- Begin output of #{opts[:command]} ----\n" - output << command_output.to_s - output << "\n---- End output of #{opts[:command]} ----\n" - end - raise Chef::Exceptions::Exec, "#{opts[:command]} returned #{status.exitstatus}, expected #{opts[:returns]}#{output}" - end - - # Call #run_command but set LC_ALL to the system's current environment so it doesn't get changed to C. - # - # === Parameters - # args<Hash>: A number of required and optional arguments that will be handed out to #run_command - # - # === Returns - # Returns the result of #run_command - def run_command_with_systems_locale(args = {}) - args[:environment] ||= {} - args[:environment]["LC_ALL"] = ENV["LC_ALL"] - run_command args - end - - # def popen4(cmd, args={}, &b) - # @@os_handler.popen4(cmd, args, &b) - # end - - # module_function :popen4 - - # FIXME: yard with @yield - def chdir_or_tmpdir(dir) - dir ||= Dir.tmpdir - unless File.directory?(dir) - raise Chef::Exceptions::Exec, "#{dir} does not exist or is not a directory" - end - Dir.chdir(dir) do - yield - end - end - - end - end -end diff --git a/lib/chef/mixin/command/unix.rb b/lib/chef/mixin/command/unix.rb deleted file mode 100644 index aa541c3637..0000000000 --- a/lib/chef/mixin/command/unix.rb +++ /dev/null @@ -1,220 +0,0 @@ -# -# Author:: Adam Jacob (<adam@chef.io>) -# Copyright:: Copyright 2008-2016, Chef Software Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -class Chef - module Mixin - module Command - module Unix - # This is taken directly from Ara T Howard's Open4 library, and then - # modified to suit the needs of Chef. Any bugs here are most likely - # my own, and not Ara's. - # - # The original appears in external/open4.rb in its unmodified form. - # - # Thanks Ara! - def popen4(cmd, args = {}, &b) - # Ruby 1.8 suffers from intermittent segfaults believed to be due to GC while IO.select - # See CHEF-2916 / CHEF-1305 - GC.disable - - # Waitlast - this is magic. - # - # Do we wait for the child process to die before we yield - # to the block, or after? That is the magic of waitlast. - # - # By default, we are waiting before we yield the block. - args[:waitlast] ||= false - - args[:user] ||= nil - unless args[:user].kind_of?(Integer) - args[:user] = Etc.getpwnam(args[:user]).uid if args[:user] - end - args[:group] ||= nil - unless args[:group].kind_of?(Integer) - args[:group] = Etc.getgrnam(args[:group]).gid if args[:group] - end - args[:environment] ||= {} - - # Default on C locale so parsing commands output can be done - # independently of the node's default locale. - # "LC_ALL" could be set to nil, in which case we also must ignore it. - unless args[:environment].has_key?("LC_ALL") - args[:environment]["LC_ALL"] = "C" - end - - pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe - - verbose = $VERBOSE - begin - $VERBOSE = nil - ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) - - cid = fork do - pw.last.close - STDIN.reopen pw.first - pw.first.close - - pr.first.close - STDOUT.reopen pr.last - pr.last.close - - pe.first.close - STDERR.reopen pe.last - pe.last.close - - STDOUT.sync = STDERR.sync = true - - if args[:group] - Process.egid = args[:group] - Process.gid = args[:group] - end - - if args[:user] - Process.euid = args[:user] - Process.uid = args[:user] - end - - args[:environment].each do |key, value| - ENV[key] = value - end - - if args[:umask] - umask = ((args[:umask].respond_to?(:oct) ? args[:umask].oct : args[:umask].to_i) & 007777) - File.umask(umask) - end - - begin - if cmd.kind_of?(Array) - Kernel.exec(*cmd) - else - Kernel.exec(cmd) - end - raise "forty-two" - rescue Exception => e - Marshal.dump(e, ps.last) - ps.last.flush - end - ps.last.close unless ps.last.closed? - exit! - end - ensure - $VERBOSE = verbose - end - - [pw.first, pr.last, pe.last, ps.last].each { |fd| fd.close } - - begin - e = Marshal.load ps.first - raise(Exception === e ? e : "unknown failure!") - rescue EOFError # If we get an EOF error, then the exec was successful - 42 - ensure - ps.first.close - end - - pw.last.sync = true - - pi = [pw.last, pr.first, pe.first] - - if b - begin - if args[:waitlast] - b[cid, *pi] - # send EOF so that if the child process is reading from STDIN - # it will actually finish up and exit - pi[0].close_write - Process.waitpid2(cid).last - else - # This took some doing. - # The trick here is to close STDIN - # Then set our end of the childs pipes to be O_NONBLOCK - # Then wait for the child to die, which means any IO it - # wants to do must be done - it's dead. If it isn't, - # it's because something totally skanky is happening, - # and we don't care. - o = StringIO.new - e = StringIO.new - - pi[0].close - - stdout = pi[1] - stderr = pi[2] - - stdout.sync = true - stderr.sync = true - - stdout.fcntl(Fcntl::F_SETFL, pi[1].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK) - stderr.fcntl(Fcntl::F_SETFL, pi[2].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK) - - stdout_finished = false - stderr_finished = false - - results = nil - - while !stdout_finished || !stderr_finished - begin - channels_to_watch = [] - channels_to_watch << stdout if !stdout_finished - channels_to_watch << stderr if !stderr_finished - ready = IO.select(channels_to_watch, nil, nil, 1.0) - rescue Errno::EAGAIN - ensure - results = Process.waitpid2(cid, Process::WNOHANG) - if results - stdout_finished = true - stderr_finished = true - end - end - - if ready && ready.first.include?(stdout) - line = results ? stdout.gets(nil) : stdout.gets - if line - o.write(line) - else - stdout_finished = true - end - end - if ready && ready.first.include?(stderr) - line = results ? stderr.gets(nil) : stderr.gets - if line - e.write(line) - else - stderr_finished = true - end - end - end - results = Process.waitpid2(cid) unless results - o.rewind - e.rewind - b[cid, pi[0], o, e] - results.last - end - ensure - pi.each { |fd| fd.close unless fd.closed? } - end - else - [cid, pw.last, pr.first, pe.first] - end - ensure - GC.enable - end - - end - end - end -end diff --git a/lib/chef/mixin/command/windows.rb b/lib/chef/mixin/command/windows.rb deleted file mode 100644 index fd45ab0467..0000000000 --- a/lib/chef/mixin/command/windows.rb +++ /dev/null @@ -1,71 +0,0 @@ -# -# Author:: Adam Jacob (<adam@chef.io>) -# Copyright:: Copyright 2008-2016, Chef Software Inc. -# Author:: Doug MacEachern (<dougm@vmware.com>) -# Copyright:: Copyright 2010-2016, VMware, 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 "open3" - -class Chef - module Mixin - module Command - module Windows - def popen4(cmd, args = {}, &b) - # By default, we are waiting before we yield the block. - args[:waitlast] ||= false - - #XXX :user, :group, :environment support? - - Open3.popen3(cmd) do |stdin, stdout, stderr, cid| - if b - if args[:waitlast] - b[cid, stdin, stdout, stderr] - # send EOF so that if the child process is reading from STDIN - # it will actually finish up and exit - stdin.close_write - else - o = StringIO.new - e = StringIO.new - - stdin.close - - stdout.sync = true - stderr.sync = true - - line = stdout.gets(nil) - if line - o.write(line) - end - line = stderr.gets(nil) - if line - e.write(line) - end - o.rewind - e.rewind - b[cid, stdin, o, e] - end - else - [cid, stdin, stdout, stderr] - end - end - $? - end - - end - end - end -end diff --git a/lib/chef/mixin/path_sanity.rb b/lib/chef/mixin/path_sanity.rb index 6a8e017bcd..c441d0770a 100644 --- a/lib/chef/mixin/path_sanity.rb +++ b/lib/chef/mixin/path_sanity.rb @@ -1,6 +1,6 @@ # # Author:: Seth Chisamore (<schisamo@chef.io>) -# Copyright:: Copyright 2011-2016, Chef Software Inc. +# Copyright:: Copyright 2011-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,25 +22,23 @@ class Chef def enforce_path_sanity(env = ENV) if Chef::Config[:enforce_path_sanity] - env["PATH"] = "" if env["PATH"].nil? - path_separator = Chef::Platform.windows? ? ";" : ":" - existing_paths = env["PATH"].split(path_separator) - # ensure the Ruby and Gem bindirs are included - # mainly for 'full-stack' Chef installs - paths_to_add = [] - paths_to_add << ruby_bindir unless sane_paths.include?(ruby_bindir) - paths_to_add << gem_bindir unless sane_paths.include?(gem_bindir) - paths_to_add << sane_paths if sane_paths - paths_to_add.flatten!.compact! - paths_to_add.each do |sane_path| - unless existing_paths.include?(sane_path) - env_path = env["PATH"].dup - env_path << path_separator unless env["PATH"].empty? - env_path << sane_path - env["PATH"] = env_path.encode("utf-8", invalid: :replace, undef: :replace) - end - end + env["PATH"] = sanitized_path(env) + end + end + + def sanitized_path(env = ENV) + env_path = env["PATH"].nil? ? "" : env["PATH"].dup + path_separator = Chef::Platform.windows? ? ";" : ":" + # ensure the Ruby and Gem bindirs are included + # mainly for 'full-stack' Chef installs + new_paths = env_path.split(path_separator) + [ ruby_bindir, gem_bindir ].compact.each do |path| + new_paths = [ path ] + new_paths unless new_paths.include?(path) + end + sane_paths.each do |path| + new_paths << path unless new_paths.include?(path) end + new_paths.join(path_separator).encode("utf-8", invalid: :replace, undef: :replace) end private diff --git a/lib/chef/mixin/shell_out.rb b/lib/chef/mixin/shell_out.rb index 18bd067989..b6e4cffe92 100644 --- a/lib/chef/mixin/shell_out.rb +++ b/lib/chef/mixin/shell_out.rb @@ -16,11 +16,12 @@ # limitations under the License. require "mixlib/shellout" -require "chef/deprecated" +require "chef/mixin/path_sanity" class Chef module Mixin module ShellOut + include Chef::Mixin::PathSanity # PREFERRED APIS: # @@ -108,6 +109,7 @@ class Chef "LC_ALL" => Chef::Config[:internal_locale], "LANGUAGE" => Chef::Config[:internal_locale], "LANG" => Chef::Config[:internal_locale], + env_path => sanitized_path, }.update(options[env_key] || {}) shell_out_command(*args, **options) end @@ -129,28 +131,6 @@ class Chef cmd end - DEPRECATED_OPTIONS = - [ [:command_log_level, :log_level], - [:command_log_prepend, :log_tag] ] - - # CHEF-3090: Deprecate command_log_level and command_log_prepend - # Patterned after https://github.com/chef/chef/commit/e1509990b559984b43e428d4d801c394e970f432 - def run_command_compatible_options(command_args) - return command_args unless command_args.last.is_a?(Hash) - - my_command_args = command_args.dup - my_options = my_command_args.last - - DEPRECATED_OPTIONS.each do |old_option, new_option| - # Edge case: someone specifies :command_log_level and 'command_log_level' in the option hash - next unless value = my_options.delete(old_option) || my_options.delete(old_option.to_s) - deprecate_option old_option, new_option - my_options[new_option] = value - end - - my_command_args - end - # Helper for sublcasses to convert an array of string args into a string. It # will compact nil or empty strings in the array and will join the array elements # with spaces, without introducing any double spaces for nil/empty elements. @@ -186,16 +166,12 @@ class Chef private def shell_out_command(*command_args) - cmd = Mixlib::ShellOut.new(*run_command_compatible_options(command_args)) + cmd = Mixlib::ShellOut.new(*command_args) cmd.live_stream ||= io_for_live_stream cmd.run_command cmd end - def deprecate_option(old_option, new_option) - Chef.deprecated :internal_api, "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 @@ -203,6 +179,14 @@ class Chef nil end end + + def env_path + if Chef::Platform.windows? + "Path" + else + "PATH" + end + end end end end diff --git a/lib/chef/mixins.rb b/lib/chef/mixins.rb index b980e81287..2306fb9e89 100644 --- a/lib/chef/mixins.rb +++ b/lib/chef/mixins.rb @@ -1,6 +1,5 @@ require "chef/mixin/shell_out" require "chef/mixin/checksum" -require "chef/mixin/command" require "chef/mixin/convert_to_class_name" require "chef/mixin/create_path" require "chef/mixin/deep_merge" diff --git a/lib/chef/node.rb b/lib/chef/node.rb index 92bdb5887b..549bde0dbb 100644 --- a/lib/chef/node.rb +++ b/lib/chef/node.rb @@ -338,6 +338,10 @@ class Chef automatic[:platform_version] = version end + def consume_ohai_data(ohai_data) + self.automatic_attrs = Chef::Mixin::DeepMerge.merge(automatic_attrs, ohai_data) + end + # Consumes the combined run_list and other attributes in +attrs+ def consume_attributes(attrs) normal_attrs_to_merge = consume_run_list(attrs) diff --git a/lib/chef/platform/rebooter.rb b/lib/chef/platform/rebooter.rb index 6829b66539..33a6e24be2 100644 --- a/lib/chef/platform/rebooter.rb +++ b/lib/chef/platform/rebooter.rb @@ -51,8 +51,7 @@ class Chef raise Chef::Exceptions::RebootFailed.new(e.message) end - raise Chef::Exceptions::Reboot.new(msg) if Chef::Application::ExitCode.enforce_rfc_062_exit_codes? - Chef::Application::ExitCode.notify_reboot_exit_code_deprecation + raise Chef::Exceptions::Reboot.new(msg) end # this is a wrapper function so Chef::Client only needs a single line of code. diff --git a/lib/chef/policy_builder/expand_node_object.rb b/lib/chef/policy_builder/expand_node_object.rb index dbed44a002..26f39e8b73 100644 --- a/lib/chef/policy_builder/expand_node_object.rb +++ b/lib/chef/policy_builder/expand_node_object.rb @@ -239,7 +239,8 @@ class Chef end def api_service - @api_service ||= Chef::ServerAPI.new(config[:chef_server_url]) + @api_service ||= Chef::ServerAPI.new(config[:chef_server_url], + { version_class: Chef::CookbookManifestVersions }) end def config diff --git a/lib/chef/policy_builder/policyfile.rb b/lib/chef/policy_builder/policyfile.rb index f0009eac6c..f84e1dc68e 100644 --- a/lib/chef/policy_builder/policyfile.rb +++ b/lib/chef/policy_builder/policyfile.rb @@ -149,7 +149,7 @@ class Chef # # @return [Chef::RunContext] def setup_run_context(specific_recipes = nil) - Chef::Cookbook::FileVendor.fetch_from_remote(http_api) + Chef::Cookbook::FileVendor.fetch_from_remote(api_service) sync_cookbooks cookbook_collection = Chef::CookbookCollection.new(cookbooks_to_sync) cookbook_collection.validate! @@ -267,7 +267,7 @@ class Chef # @api private def policy - @policy ||= http_api.get(policyfile_location) + @policy ||= api_service.get(policyfile_location) rescue Net::HTTPServerException => e raise ConfigurationError, "Error loading policyfile from `#{policyfile_location}': #{e.class} - #{e.message}" end @@ -452,8 +452,9 @@ class Chef end # @api private - def http_api - @api_service ||= Chef::ServerAPI.new(config[:chef_server_url]) + def api_service + @api_service ||= Chef::ServerAPI.new(config[:chef_server_url], + { version_class: Chef::CookbookManifestVersions }) end # @api private @@ -496,7 +497,7 @@ class Chef def compat_mode_manifest_for(cookbook_name, lock_data) xyz_version = lock_data["dotted_decimal_identifier"] rel_url = "cookbooks/#{cookbook_name}/#{xyz_version}" - inflate_cbv_object(http_api.get(rel_url)) + inflate_cbv_object(api_service.get(rel_url)) rescue Exception => e message = "Error loading cookbook #{cookbook_name} at version #{xyz_version} from #{rel_url}: #{e.class} - #{e.message}" err = Chef::Exceptions::CookbookNotFound.new(message) @@ -507,7 +508,7 @@ class Chef def artifact_manifest_for(cookbook_name, lock_data) identifier = lock_data["identifier"] rel_url = "cookbook_artifacts/#{cookbook_name}/#{identifier}" - inflate_cbv_object(http_api.get(rel_url)) + inflate_cbv_object(api_service.get(rel_url)) rescue Exception => e message = "Error loading cookbook #{cookbook_name} with identifier #{identifier} from #{rel_url}: #{e.class} - #{e.message}" err = Chef::Exceptions::CookbookNotFound.new(message) diff --git a/lib/chef/property.rb b/lib/chef/property.rb index c6f72e15a7..a72e41a61e 100644 --- a/lib/chef/property.rb +++ b/lib/chef/property.rb @@ -110,6 +110,20 @@ class Chef raise ArgumentError, "Cannot specify both default and name_property/name_attribute together on property #{self}" end + # Recursively freeze the default if it isn't a lazy value. + unless default.is_a?(DelayedEvaluator) + visitor = lambda do |obj| + case obj + when Hash + obj.each_value { |value| visitor.call(value) } + when Array + obj.each { |value| visitor.call(value) } + end + obj.freeze + end + visitor.call(default) + end + # Validate the default early, so the user gets a good error message, and # cache it so we don't do it again if so begin diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb index 8334eab0a6..c7048d50e5 100644 --- a/lib/chef/provider.rb +++ b/lib/chef/provider.rb @@ -51,6 +51,32 @@ class Chef true end + # Defines an action method on the provider, running the block to compile the + # resources, converging them, and then checking if any were updated (and + # updating new-resource if so) + # + # @since 13.0 + # @param name [String, Symbol] Name of the action to define. + # @param block [Proc] Body of the action. + # @return [void] + def self.action(name, &block) + # We need the block directly in a method so that `super` works. + define_method("compile_action_#{name}", &block) + class_eval <<-EOM + def action_#{name} + compile_and_converge_action { compile_action_#{name} } + end + EOM + end + + # Deprecation stub for the old use_inline_resources mode. + # + # @return [void] + def self.use_inline_resources + # Uncomment this in Chef 13.6. + # Chef.deprecated(:use_inline_resources, "The use_inline_resources mode is no longer optional and the line enabling it can be removed") + end + #-- # TODO: this should be a reader, and the action should be passed in the # constructor; however, many/most subclasses override the constructor so @@ -176,6 +202,22 @@ class Chef converge_actions.add_action(descriptions, &block) end + # Create a child run_context, compile the block, and converge it. + # + # @api private + def compile_and_converge_action(&block) + old_run_context = run_context + @run_context = run_context.create_child + return_value = instance_eval(&block) + Chef::Runner.new(run_context).converge + return_value + ensure + if run_context.resource_collection.any? { |r| r.updated? } + new_resource.updated_by_last_action(true) + end + @run_context = old_run_context + end + # # Handle patchy convergence safely. # @@ -326,84 +368,6 @@ class Chef end end - # Enables inline evaluation of resources in provider actions. - # - # Without this option, any resources declared inside the Provider are added - # to the resource collection after the current position at the time the - # action is executed. Because they are added to the primary resource - # collection for the chef run, they can notify other resources outside - # the Provider, and potentially be notified by resources outside the Provider - # (but this is complicated by the fact that they don't exist until the - # provider executes). In this mode, it is impossible to correctly set the - # updated_by_last_action flag on the parent Provider resource, since it - # executes and returns before its component resources are run. - # - # With this option enabled, each action creates a temporary run_context - # with its own resource collection, evaluates the action's code in that - # context, and then converges the resources created. If any resources - # were updated, then this provider's new_resource will be marked updated. - # - # In this mode, resources created within the Provider cannot interact with - # external resources via notifies, though notifications to other - # resources within the Provider will work. Delayed notifications are executed - # at the conclusion of the provider's action, *not* at the end of the - # main chef run. - # - # This mode of evaluation is experimental, but is believed to be a better - # set of tradeoffs than the append-after mode, so it will likely become - # the default in a future major release of Chef. - # - def self.use_inline_resources - extend InlineResources::ClassMethods - include InlineResources - end - - # Chef::Provider::InlineResources - # Implementation of inline resource convergence for providers. See - # Provider.use_inline_resources for a longer explanation. - # - # This code is restricted to a module so that it can be selectively - # applied to providers on an opt-in basis. - # - # @api private - module InlineResources - - # Create a child run_context, compile the block, and converge it. - # - # @api private - def compile_and_converge_action(&block) - old_run_context = run_context - @run_context = run_context.create_child - return_value = instance_eval(&block) - Chef::Runner.new(run_context).converge - return_value - ensure - if run_context.resource_collection.any? { |r| r.updated? } - new_resource.updated_by_last_action(true) - end - @run_context = old_run_context - end - - # Class methods for InlineResources. Overrides the `action` DSL method - # with one that enables inline resource convergence. - # - # @api private - module ClassMethods - # Defines an action method on the provider, running the block to - # compile the resources, converging them, and then checking if any - # were updated (and updating new-resource if so) - def action(name, &block) - # We need the block directly in a method so that `super` works - define_method("compile_action_#{name}", &block) - class_eval <<-EOM - def action_#{name} - compile_and_converge_action { compile_action_#{name} } - end - EOM - end - end - end - protected def converge_actions @@ -432,33 +396,6 @@ class Chef end end - module DeprecatedLWRPClass - def const_missing(class_name) - if Chef::Provider.deprecated_constants[class_name.to_sym] - Chef.deprecated(:custom_resource, "Using an LWRP provider by its name (#{class_name}) directly is no longer supported in Chef 12 and will be removed. Use Chef::ProviderResolver.new(node, resource, action) instead.") - Chef::Provider.deprecated_constants[class_name.to_sym] - else - raise NameError, "uninitialized constant Chef::Provider::#{class_name}" - end - end - - # @api private - def register_deprecated_lwrp_class(provider_class, class_name) - # Register Chef::Provider::MyProvider with deprecation warnings if you - # try to access it - if Chef::Provider.const_defined?(class_name, false) - Chef::Log.warn "Chef::Provider::#{class_name} already exists! Cannot create deprecation class for #{provider_class}" - else - Chef::Provider.deprecated_constants[class_name.to_sym] = provider_class - end - end - - def deprecated_constants - raise "Deprecated constants should be called only on Chef::Provider" unless self == Chef::Provider - @deprecated_constants ||= {} - end - end - extend DeprecatedLWRPClass end end diff --git a/lib/chef/provider/apt_repository.rb b/lib/chef/provider/apt_repository.rb index 2c72849d1c..541f733e7b 100644 --- a/lib/chef/provider/apt_repository.rb +++ b/lib/chef/provider/apt_repository.rb @@ -26,8 +26,6 @@ require "chef/provider/noop" class Chef class Provider class AptRepository < Chef::Provider - use_inline_resources - include Chef::Mixin::ShellOut extend Chef::Mixin::Which diff --git a/lib/chef/provider/apt_update.rb b/lib/chef/provider/apt_update.rb index 135bd64035..670f3ad7f6 100644 --- a/lib/chef/provider/apt_update.rb +++ b/lib/chef/provider/apt_update.rb @@ -23,8 +23,6 @@ require "chef/mixin/which" class Chef class Provider class AptUpdate < Chef::Provider - use_inline_resources - extend Chef::Mixin::Which provides :apt_update do diff --git a/lib/chef/provider/cron.rb b/lib/chef/provider/cron.rb index a45e889bcc..c232585c18 100644 --- a/lib/chef/provider/cron.rb +++ b/lib/chef/provider/cron.rb @@ -17,13 +17,11 @@ # require "chef/log" -require "chef/mixin/command" require "chef/provider" class Chef class Provider class Cron < Chef::Provider - include Chef::Mixin::Command provides :cron, os: ["!aix", "!solaris2"] @@ -202,30 +200,18 @@ class Chef end def read_crontab - crontab = nil - status = popen4("crontab -l -u #{new_resource.user}") do |pid, stdin, stdout, stderr| - crontab = stdout.read - end - if status.exitstatus > 1 - raise Chef::Exceptions::Cron, "Error determining state of #{new_resource.name}, exit: #{status.exitstatus}" - end - crontab + so = shell_out!("crontab -l -u #{new_resource.user}", returns: [0, 1]) + return nil if so.exitstatus == 1 + so.stdout + rescue => e + raise Chef::Exceptions::Cron, "Error determining state of #{new_resource.name}, error: #{e}" end def write_crontab(crontab) write_exception = false - status = popen4("crontab -u #{new_resource.user} -", :waitlast => true) do |pid, stdin, stdout, stderr| - begin - stdin.write crontab - rescue Errno::EPIPE => e - # popen4 could yield while child has already died. - write_exception = true - Chef::Log.debug("#{e.message}") - end - end - if status.exitstatus > 0 || write_exception - raise Chef::Exceptions::Cron, "Error updating state of #{new_resource.name}, exit: #{status.exitstatus}" - end + so = shell_out!("crontab -u #{new_resource.user} -", input: crontab) + rescue => e + raise Chef::Exceptions::Cron, "Error updating state of #{new_resource.name}, error: #{e}" end def get_crontab_entry diff --git a/lib/chef/provider/deploy.rb b/lib/chef/provider/deploy.rb index 172705ac71..c4229d2441 100644 --- a/lib/chef/provider/deploy.rb +++ b/lib/chef/provider/deploy.rb @@ -16,7 +16,6 @@ # limitations under the License. # -require "chef/mixin/command" require "chef/mixin/from_file" require "chef/provider/git" require "chef/provider/subversion" @@ -29,7 +28,6 @@ class Chef include Chef::DSL::Recipe include Chef::Mixin::FromFile - include Chef::Mixin::Command attr_reader :scm_provider, :release_path, :shared_path, :previous_release_path diff --git a/lib/chef/provider/env.rb b/lib/chef/provider/env.rb index 7777da183d..490fa31146 100644 --- a/lib/chef/provider/env.rb +++ b/lib/chef/provider/env.rb @@ -17,13 +17,11 @@ # require "chef/provider" -require "chef/mixin/command" require "chef/resource/env" class Chef class Provider class Env < Chef::Provider - include Chef::Mixin::Command attr_accessor :key_exists provides :env, os: "!windows" diff --git a/lib/chef/provider/erl_call.rb b/lib/chef/provider/erl_call.rb index 26ac19d03b..b73341bb16 100644 --- a/lib/chef/provider/erl_call.rb +++ b/lib/chef/provider/erl_call.rb @@ -17,13 +17,11 @@ # require "chef/log" -require "chef/mixin/command" require "chef/provider" class Chef class Provider class ErlCall < Chef::Provider - include Chef::Mixin::Command provides :erl_call @@ -58,44 +56,18 @@ class Chef command = "erl_call -e #{distributed} #{node} #{cookie}" converge_by("run erlang block") do - begin - pid, stdin, stdout, stderr = popen4(command, :waitlast => true) + so = shell_out!(command, input: new_resource.code) - Chef::Log.debug("#{new_resource} running") - Chef::Log.debug("#{new_resource} command: #{command}") - Chef::Log.debug("#{new_resource} code: #{new_resource.code}") - - new_resource.code.each_line { |line| stdin.puts(line.chomp) } - - stdin.close - - Chef::Log.debug("#{new_resource} output: ") - - stdout_output = "" - stdout.each_line { |line| stdout_output << line } - stdout.close - - stderr_output = "" - stderr.each_line { |line| stderr_output << line } - stderr.close - - # fail if stderr contains anything - if stderr_output.length > 0 - raise Chef::Exceptions::ErlCall, stderr_output - end - - # fail if the first 4 characters aren't "{ok," - unless stdout_output[0..3].include?("{ok,") - raise Chef::Exceptions::ErlCall, stdout_output - end - - new_resource.updated_by_last_action(true) + # fail if stderr contains anything + if so.stderr.length > 0 + raise Chef::Exceptions::ErlCall, so.stderr + end - Chef::Log.debug("#{new_resource} #{stdout_output}") - Chef::Log.info("#{@new_resouce} ran successfully") - ensure - Process.wait(pid) if pid + # fail if the first 4 characters aren't "{ok," + unless so.stdout[0..3].include?("{ok,") + raise Chef::Exceptions::ErlCall, so.stdout end + end end diff --git a/lib/chef/provider/group.rb b/lib/chef/provider/group.rb index 82196c72f3..ce20a2b5e5 100644 --- a/lib/chef/provider/group.rb +++ b/lib/chef/provider/group.rb @@ -18,14 +18,12 @@ require "chef/provider" require "chef/mixin/shell_out" -require "chef/mixin/command" require "etc" class Chef class Provider class Group < Chef::Provider include Chef::Mixin::ShellOut - include Chef::Mixin::Command attr_accessor :group_exists attr_accessor :change_desc diff --git a/lib/chef/provider/ifconfig.rb b/lib/chef/provider/ifconfig.rb index 003cc3b0e0..16bbe8124b 100644 --- a/lib/chef/provider/ifconfig.rb +++ b/lib/chef/provider/ifconfig.rb @@ -17,7 +17,6 @@ # require "chef/log" -require "chef/mixin/command" require "chef/mixin/shell_out" require "chef/provider" require "chef/resource/file" @@ -42,7 +41,6 @@ class Chef provides :ifconfig include Chef::Mixin::ShellOut - include Chef::Mixin::Command attr_accessor :config_template attr_accessor :config_path diff --git a/lib/chef/provider/ifconfig/redhat.rb b/lib/chef/provider/ifconfig/redhat.rb index 841e725b94..8af9f10f67 100644 --- a/lib/chef/provider/ifconfig/redhat.rb +++ b/lib/chef/provider/ifconfig/redhat.rb @@ -22,7 +22,7 @@ class Chef class Provider class Ifconfig class Redhat < Chef::Provider::Ifconfig - provides :ifconfig, platform_family: %w{fedora rhel} + provides :ifconfig, platform_family: %w{fedora rhel amazon} def initialize(new_resource, run_context) super(new_resource, run_context) diff --git a/lib/chef/provider/launchd.rb b/lib/chef/provider/launchd.rb index a58954c707..9c368c2b48 100644 --- a/lib/chef/provider/launchd.rb +++ b/lib/chef/provider/launchd.rb @@ -85,6 +85,10 @@ class Chef manage_service(:disable) end + def action_restart + manage_service(:restart) + end + def manage_plist(action) if source res = cookbook_file_resource diff --git a/lib/chef/provider/lwrp_base.rb b/lib/chef/provider/lwrp_base.rb index cbf25f1e4f..09f90b7403 100644 --- a/lib/chef/provider/lwrp_base.rb +++ b/lib/chef/provider/lwrp_base.rb @@ -75,18 +75,9 @@ class Chef LWRPBase.loaded_lwrps[filename] = true - Chef::Provider.register_deprecated_lwrp_class(provider_class, convert_to_class_name(resource_name)) - provider_class end - # DSL for defining a provider's actions. - def action(name, &block) - define_method("action_#{name}") do - instance_eval(&block) - end - end - protected def loaded_lwrps diff --git a/lib/chef/provider/mdadm.rb b/lib/chef/provider/mdadm.rb index 5c972462bc..7d7b26acfa 100644 --- a/lib/chef/provider/mdadm.rb +++ b/lib/chef/provider/mdadm.rb @@ -25,10 +25,6 @@ class Chef provides :mdadm - def popen4 - raise Exception, "deprecated" - end - def load_current_resource @current_resource = Chef::Resource::Mdadm.new(new_resource.name) current_resource.raid_device(new_resource.raid_device) diff --git a/lib/chef/provider/ohai.rb b/lib/chef/provider/ohai.rb index 0c2397cee5..42fc450271 100644 --- a/lib/chef/provider/ohai.rb +++ b/lib/chef/provider/ohai.rb @@ -21,8 +21,6 @@ require "ohai" class Chef class Provider class Ohai < Chef::Provider - use_inline_resources - provides :ohai def load_current_resource diff --git a/lib/chef/provider/osx_profile.rb b/lib/chef/provider/osx_profile.rb index 8ecde54ce0..1d87f29eb2 100644 --- a/lib/chef/provider/osx_profile.rb +++ b/lib/chef/provider/osx_profile.rb @@ -25,7 +25,6 @@ require "uuidtools" class Chef class Provider class OsxProfile < Chef::Provider - include Chef::Mixin::Command provides :osx_profile, os: "darwin" provides :osx_config_profile, os: "darwin" diff --git a/lib/chef/provider/package.rb b/lib/chef/provider/package.rb index 36df048741..4b73f47ed3 100644 --- a/lib/chef/provider/package.rb +++ b/lib/chef/provider/package.rb @@ -17,7 +17,6 @@ # require "chef/mixin/shell_out" -require "chef/mixin/command" require "chef/mixin/subclass_directive" require "chef/log" require "chef/file_cache" @@ -28,12 +27,9 @@ require "shellwords" class Chef class Provider class Package < Chef::Provider - include Chef::Mixin::Command include Chef::Mixin::ShellOut extend Chef::Mixin::SubclassDirective - use_inline_resources - # subclasses declare this if they want all their arguments as arrays of packages and names subclass_directive :use_multipackage_api # subclasses declare this if they want sources (filenames) pulled from their package names @@ -54,11 +50,7 @@ class Chef end def options - if new_resource.options.is_a?(String) - new_resource.options.shellsplit - else - new_resource.options - end + new_resource.options end def check_resource_semantics! @@ -305,7 +297,11 @@ class Chef def expand_options(options) # its deprecated but still work to do to deprecate it fully #Chef.deprecated(:package_misc, "expand_options is deprecated, use shell_out_compact or shell_out_compact_timeout instead") - options ? " #{options}" : "" + if options + " #{options.is_a?(Array) ? Shellwords.join(options) : options}" + else + "" + end end # Check the current_version against either the candidate_version or the new_version diff --git a/lib/chef/provider/package/aix.rb b/lib/chef/provider/package/aix.rb index 5af5f5afad..b013b3d8ce 100644 --- a/lib/chef/provider/package/aix.rb +++ b/lib/chef/provider/package/aix.rb @@ -17,7 +17,6 @@ # # require "chef/provider/package" -require "chef/mixin/command" require "chef/resource/package" require "chef/mixin/get_source_from_package" diff --git a/lib/chef/provider/package/cab.rb b/lib/chef/provider/package/cab.rb index d6e989eb72..3a8cc507f9 100644 --- a/lib/chef/provider/package/cab.rb +++ b/lib/chef/provider/package/cab.rb @@ -26,7 +26,6 @@ class Chef class Provider class Package class Cab < Chef::Provider::Package - use_inline_resources include Chef::Mixin::ShellOut include Chef::Mixin::Uris include Chef::Mixin::Checksum diff --git a/lib/chef/provider/package/dnf.rb b/lib/chef/provider/package/dnf.rb index c551ae7cb0..42d679c940 100644 --- a/lib/chef/provider/package/dnf.rb +++ b/lib/chef/provider/package/dnf.rb @@ -35,7 +35,7 @@ class Chef use_multipackage_api use_package_name_for_source - provides :package, platform_family: %w{rhel fedora} do + provides :package, platform_family: %w{rhel fedora amazon} do which("dnf") && shell_out("rpm -q dnf").stdout =~ /^dnf-[1-9]/ end diff --git a/lib/chef/provider/package/ips.rb b/lib/chef/provider/package/ips.rb index 9666013cc3..d0c8bed175 100644 --- a/lib/chef/provider/package/ips.rb +++ b/lib/chef/provider/package/ips.rb @@ -19,7 +19,6 @@ require "open3" require "chef/provider/package" -require "chef/mixin/command" require "chef/resource/package" class Chef diff --git a/lib/chef/provider/package/msu.rb b/lib/chef/provider/package/msu.rb index fe4a11461f..15e18feba4 100644 --- a/lib/chef/provider/package/msu.rb +++ b/lib/chef/provider/package/msu.rb @@ -32,7 +32,6 @@ class Chef class Provider class Package class Msu < Chef::Provider::Package - use_inline_resources include Chef::Mixin::ShellOut include Chef::Mixin::Uris include Chef::Mixin::Checksum diff --git a/lib/chef/provider/package/pacman.rb b/lib/chef/provider/package/pacman.rb index 25683687b2..d1830bdefa 100644 --- a/lib/chef/provider/package/pacman.rb +++ b/lib/chef/provider/package/pacman.rb @@ -17,7 +17,6 @@ # require "chef/provider/package" -require "chef/mixin/command" require "chef/resource/package" class Chef diff --git a/lib/chef/provider/package/portage.rb b/lib/chef/provider/package/portage.rb index fd96dfa47f..05a5df370e 100644 --- a/lib/chef/provider/package/portage.rb +++ b/lib/chef/provider/package/portage.rb @@ -17,7 +17,6 @@ # require "chef/provider/package" -require "chef/mixin/command" require "chef/resource/package" require "chef/util/path_helper" diff --git a/lib/chef/provider/package/rpm.rb b/lib/chef/provider/package/rpm.rb index 1701886191..7ec24f8c24 100644 --- a/lib/chef/provider/package/rpm.rb +++ b/lib/chef/provider/package/rpm.rb @@ -16,7 +16,6 @@ # limitations under the License. # require "chef/provider/package" -require "chef/mixin/command" require "chef/resource/package" require "chef/mixin/get_source_from_package" diff --git a/lib/chef/provider/package/rubygems.rb b/lib/chef/provider/package/rubygems.rb index 1019b8d3fa..69936b3d58 100644 --- a/lib/chef/provider/package/rubygems.rb +++ b/lib/chef/provider/package/rubygems.rb @@ -19,7 +19,6 @@ require "uri" require "chef/provider/package" -require "chef/mixin/command" require "chef/resource/package" require "chef/mixin/get_source_from_package" @@ -424,6 +423,7 @@ class Chef def source_is_remote? return true if new_resource.source.nil? + return true if new_resource.source.is_a?(Array) scheme = URI.parse(new_resource.source).scheme # URI.parse gets confused by MS Windows paths with forward slashes. scheme = nil if scheme =~ /^[a-z]$/ @@ -470,7 +470,9 @@ class Chef end def gem_sources - new_resource.source ? Array(new_resource.source) : nil + srcs = [ new_resource.source ] + srcs << Chef::Config[:rubygems_url] if new_resource.include_default_source + srcs.flatten.compact end def load_current_resource @@ -534,18 +536,18 @@ class Chef end def install_via_gem_command(name, version) - if new_resource.source =~ /\.gem$/i + src = [] + if new_resource.source.is_a?(String) && new_resource.source =~ /\.gem$/i name = new_resource.source - elsif new_resource.clear_sources - src = " --clear-sources" - src << (new_resource.source && " --source=#{new_resource.source}" || "") else - src = new_resource.source && " --source=#{new_resource.source} --source=#{Chef::Config[:rubygems_url]}" + src << "--clear-sources" if new_resource.clear_sources + src += gem_sources.map { |s| "--source=#{s}" } end + src_str = src.empty? ? "" : " #{src.join(" ")}" if !version.nil? && !version.empty? - shell_out_with_timeout!("#{gem_binary_path} install #{name} -q --no-rdoc --no-ri -v \"#{version}\"#{src}#{opts}", env: nil) + shell_out_with_timeout!("#{gem_binary_path} install #{name} -q --no-rdoc --no-ri -v \"#{version}\"#{src_str}#{opts}", env: nil) else - shell_out_with_timeout!("#{gem_binary_path} install \"#{name}\" -q --no-rdoc --no-ri #{src}#{opts}", env: nil) + shell_out_with_timeout!("#{gem_binary_path} install \"#{name}\" -q --no-rdoc --no-ri #{src_str}#{opts}", env: nil) end end diff --git a/lib/chef/provider/package/solaris.rb b/lib/chef/provider/package/solaris.rb index 5537127310..f6e66c050a 100644 --- a/lib/chef/provider/package/solaris.rb +++ b/lib/chef/provider/package/solaris.rb @@ -16,7 +16,6 @@ # limitations under the License. # require "chef/provider/package" -require "chef/mixin/command" require "chef/resource/package" require "chef/mixin/get_source_from_package" diff --git a/lib/chef/provider/package/windows/msi.rb b/lib/chef/provider/package/windows/msi.rb index 7e6048ce49..ef8b674b60 100644 --- a/lib/chef/provider/package/windows/msi.rb +++ b/lib/chef/provider/package/windows/msi.rb @@ -1,6 +1,6 @@ # # Author:: Bryan McLellan <btm@loftninjas.org> -# Copyright:: Copyright 2014-2016, Chef Software, Inc. +# Copyright:: Copyright 2014-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/lib/chef/provider/package/yum.rb b/lib/chef/provider/package/yum.rb index d37aa1fb73..d87e421409 100644 --- a/lib/chef/provider/package/yum.rb +++ b/lib/chef/provider/package/yum.rb @@ -1,6 +1,6 @@ # Author:: Adam Jacob (<adam@chef.io>) -# Copyright:: Copyright 2008-2017, Chef Software, Inc. +# Copyright:: Copyright 2008-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,7 +29,7 @@ class Chef class Yum < Chef::Provider::Package include Chef::Mixin::GetSourceFromPackage - provides :package, platform_family: %w{rhel fedora} + provides :package, platform_family: %w{rhel fedora amazon} provides :yum_package, os: "linux" # Multipackage API @@ -194,7 +194,7 @@ class Chef def manage_extra_repo_control if new_resource.options repo_control = [] - new_resource.options.split.each do |opt| + new_resource.options.each do |opt| repo_control << opt if opt =~ /--(enable|disable)repo=.+/ end diff --git a/lib/chef/provider/package/zypper.rb b/lib/chef/provider/package/zypper.rb index 45c6c91f60..7b8fd6c426 100644 --- a/lib/chef/provider/package/zypper.rb +++ b/lib/chef/provider/package/zypper.rb @@ -2,7 +2,7 @@ # # Authors:: Adam Jacob (<adam@chef.io>) # Ionuț Arțăriși (<iartarisi@suse.cz>) -# Copyright:: Copyright 2008-2016, Chef Software, Inc. +# Copyright:: Copyright 2008-2017, Chef Software Inc. # Copyright 2013-2016, SUSE Linux GmbH # License:: Apache License, Version 2.0 # @@ -145,17 +145,7 @@ class Chef end def gpg_checks - case Chef::Config[:zypper_check_gpg] - when true - nil - when false - "--no-gpg-checks" - when nil - Chef::Log.warn("Chef::Config[:zypper_check_gpg] was not set. " \ - "All packages will be installed without gpg signature checks. " \ - "This is a security hazard.") - "--no-gpg-checks" - end + "--no-gpg-checks" unless new_resource.gpg_check end end end diff --git a/lib/chef/provider/route.rb b/lib/chef/provider/route.rb index 2e2a1266b4..59d516be6a 100644 --- a/lib/chef/provider/route.rb +++ b/lib/chef/provider/route.rb @@ -17,14 +17,12 @@ # require "chef/log" -require "chef/mixin/command" require "chef/provider" require "ipaddr" class Chef class Provider class Route < Chef::Provider - include Chef::Mixin::Command provides :route diff --git a/lib/chef/provider/service.rb b/lib/chef/provider/service.rb index 11d04eaca2..34ecf2f0bc 100644 --- a/lib/chef/provider/service.rb +++ b/lib/chef/provider/service.rb @@ -17,15 +17,12 @@ # limitations under the License. # -require "chef/mixin/command" require "chef/provider" class Chef class Provider class Service < Chef::Provider - include Chef::Mixin::Command - def supports @supports ||= new_resource.supports.dup end @@ -248,7 +245,7 @@ class Chef Chef.set_provider_priority_array :service, [ Systemd, Arch ], platform_family: "arch" Chef.set_provider_priority_array :service, [ Systemd, Gentoo ], platform_family: "gentoo" Chef.set_provider_priority_array :service, [ Systemd, Upstart, Insserv, Debian, Invokercd ], platform_family: "debian" - Chef.set_provider_priority_array :service, [ Systemd, Insserv, Redhat ], platform_family: %w{rhel fedora suse} + Chef.set_provider_priority_array :service, [ Systemd, Insserv, Redhat ], platform_family: %w{rhel fedora suse amazon} end end end diff --git a/lib/chef/provider/service/debian.rb b/lib/chef/provider/service/debian.rb index 9d11032055..58a43d27f8 100644 --- a/lib/chef/provider/service/debian.rb +++ b/lib/chef/provider/service/debian.rb @@ -1,6 +1,6 @@ # # Author:: AJ Christensen (<aj@hjksolutions.com>) -# Copyright:: Copyright 2008-2016, Chef Software Inc. +# Copyright:: Copyright 2008-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,8 +35,6 @@ class Chef def load_current_resource super - @priority_success = true - @rcd_status = nil current_resource.priority(get_priority) current_resource.enabled(service_currently_enabled?(current_resource.priority)) current_resource @@ -54,8 +52,8 @@ class Chef end requirements.assert(:all_actions) do |a| - a.assertion { @priority_success } - a.failure_message Chef::Exceptions::Service, "/usr/sbin/update-rc.d -n -f #{current_resource.service_name} failed - #{@rcd_status.inspect}" + a.assertion { @so_priority.exitstatus == 0 } + a.failure_message Chef::Exceptions::Service, "/usr/sbin/update-rc.d -n -f #{current_resource.service_name} failed - #{@so_priority.inspect}" # This can happen if the service is not yet installed,so we'll fake it. a.whyrun ["Unable to determine priority of service, assuming service would have been correctly installed earlier in the run.", "Assigning temporary priorities to continue.", @@ -75,19 +73,18 @@ class Chef def get_priority priority = {} - @rcd_status = popen4("/usr/sbin/update-rc.d -n -f #{current_resource.service_name} remove") do |pid, stdin, stdout, stderr| - - [stdout, stderr].each do |iop| - iop.each_line do |line| - if UPDATE_RC_D_PRIORITIES =~ line - # priority[runlevel] = [ S|K, priority ] - # S = Start, K = Kill - # debian runlevels: 0 Halt, 1 Singleuser, 2 Multiuser, 3-5 == 2, 6 Reboot - priority[$1] = [($2 == "S" ? :start : :stop), $3] - end - if line =~ UPDATE_RC_D_ENABLED_MATCHES - enabled = true - end + @so_priority = shell_out!("/usr/sbin/update-rc.d -n -f #{current_resource.service_name} remove") + + [@so_priority.stdout, @so_priority.stderr].each do |iop| + iop.each_line do |line| + if UPDATE_RC_D_PRIORITIES =~ line + # priority[runlevel] = [ S|K, priority ] + # S = Start, K = Kill + # debian runlevels: 0 Halt, 1 Singleuser, 2 Multiuser, 3-5 == 2, 6 Reboot + priority[$1] = [($2 == "S" ? :start : :stop), $3] + end + if line =~ UPDATE_RC_D_ENABLED_MATCHES + enabled = true end end end @@ -98,9 +95,6 @@ class Chef priority = priority[2].last end - unless @rcd_status.exitstatus == 0 - @priority_success = false - end priority end diff --git a/lib/chef/provider/service/freebsd.rb b/lib/chef/provider/service/freebsd.rb index 9746dfdef0..c1e315afee 100644 --- a/lib/chef/provider/service/freebsd.rb +++ b/lib/chef/provider/service/freebsd.rb @@ -18,7 +18,6 @@ require "chef/resource/service" require "chef/provider/service/init" -require "chef/mixin/command" class Chef class Provider diff --git a/lib/chef/provider/service/gentoo.rb b/lib/chef/provider/service/gentoo.rb index 7bb57113ac..0ac74649b6 100644 --- a/lib/chef/provider/service/gentoo.rb +++ b/lib/chef/provider/service/gentoo.rb @@ -18,7 +18,6 @@ # require "chef/provider/service/init" -require "chef/mixin/command" require "chef/util/path_helper" class Chef::Provider::Service::Gentoo < Chef::Provider::Service::Init diff --git a/lib/chef/provider/service/init.rb b/lib/chef/provider/service/init.rb index dff627d016..c6c582f8b8 100644 --- a/lib/chef/provider/service/init.rb +++ b/lib/chef/provider/service/init.rb @@ -17,7 +17,6 @@ # require "chef/provider/service/simple" -require "chef/mixin/command" require "chef/platform/service_helpers" class Chef diff --git a/lib/chef/provider/service/insserv.rb b/lib/chef/provider/service/insserv.rb index c3dca10495..a8e841f8b3 100644 --- a/lib/chef/provider/service/insserv.rb +++ b/lib/chef/provider/service/insserv.rb @@ -1,6 +1,6 @@ # # Author:: Bryan McLellan <btm@loftninjas.org> -# Copyright:: Copyright 2011-2016, Chef Software Inc. +# Copyright:: Copyright 2011-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,7 @@ class Chef class Service class Insserv < Chef::Provider::Service::Init - provides :service, platform_family: %w{debian rhel fedora suse} do |node| + provides :service, platform_family: %w{debian rhel fedora suse amazon} do |node| Chef::Platform::ServiceHelpers.service_resource_providers.include?(:insserv) end diff --git a/lib/chef/provider/service/openbsd.rb b/lib/chef/provider/service/openbsd.rb index 780337e1b6..f839d54780 100644 --- a/lib/chef/provider/service/openbsd.rb +++ b/lib/chef/provider/service/openbsd.rb @@ -16,7 +16,6 @@ # limitations under the License. # -require "chef/mixin/command" require "chef/mixin/shell_out" require "chef/provider/service/init" require "chef/resource/service" diff --git a/lib/chef/provider/service/redhat.rb b/lib/chef/provider/service/redhat.rb index 21ab678706..1da3d7c01a 100644 --- a/lib/chef/provider/service/redhat.rb +++ b/lib/chef/provider/service/redhat.rb @@ -1,6 +1,6 @@ # # Author:: AJ Christensen (<aj@hjksolutions.com>) -# Copyright:: Copyright 2008-2016, Chef Software, Inc. +# Copyright:: Copyright 2008-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,7 +28,7 @@ class Chef # @api private attr_accessor :current_run_levels - provides :service, platform_family: %w{rhel fedora suse} do |node| + provides :service, platform_family: %w{rhel fedora suse amazon} do |node| Chef::Platform::ServiceHelpers.service_resource_providers.include?(:redhat) end diff --git a/lib/chef/provider/service/simple.rb b/lib/chef/provider/service/simple.rb index 84ced52071..81ac970b87 100644 --- a/lib/chef/provider/service/simple.rb +++ b/lib/chef/provider/service/simple.rb @@ -18,7 +18,6 @@ require "chef/provider/service" require "chef/resource/service" -require "chef/mixin/command" class Chef class Provider diff --git a/lib/chef/provider/service/solaris.rb b/lib/chef/provider/service/solaris.rb index c560bed011..9c85fda05f 100644 --- a/lib/chef/provider/service/solaris.rb +++ b/lib/chef/provider/service/solaris.rb @@ -18,7 +18,6 @@ require "chef/provider/service" require "chef/resource/service" -require "chef/mixin/command" class Chef class Provider diff --git a/lib/chef/provider/service/upstart.rb b/lib/chef/provider/service/upstart.rb index 9c0d97d376..9783b3b3a5 100644 --- a/lib/chef/provider/service/upstart.rb +++ b/lib/chef/provider/service/upstart.rb @@ -18,7 +18,6 @@ require "chef/resource/service" require "chef/provider/service/simple" -require "chef/mixin/command" require "chef/util/file_edit" class Chef @@ -241,17 +240,16 @@ class Chef def upstart_goal_state command = "/sbin/status #{@job}" - status = popen4(command) do |pid, stdin, stdout, stderr| - stdout.each_line do |line| - # service goal/state - # OR - # service (instance) goal/state - # OR - # service (goal) state - line =~ UPSTART_STATE_FORMAT - data = Regexp.last_match - return data[1] - end + so = shell_out(command) + so.stdout.each_line do |line| + # service goal/state + # OR + # service (instance) goal/state + # OR + # service (goal) state + line =~ UPSTART_STATE_FORMAT + data = Regexp.last_match + return data[1] end end diff --git a/lib/chef/provider/subversion.rb b/lib/chef/provider/subversion.rb index d7e26f3968..4fece0ae40 100644 --- a/lib/chef/provider/subversion.rb +++ b/lib/chef/provider/subversion.rb @@ -20,7 +20,6 @@ require "chef/log" require "chef/provider" -require "chef/mixin/command" require "chef-config/mixin/fuzzy_hostname_matcher" require "fileutils" @@ -32,7 +31,6 @@ class Chef SVN_INFO_PATTERN = /^([\w\s]+): (.+)$/ - include Chef::Mixin::Command include ChefConfig::Mixin::FuzzyHostnameMatcher def load_current_resource diff --git a/lib/chef/provider/systemd_unit.rb b/lib/chef/provider/systemd_unit.rb index 5175dc6be9..143efe7b91 100644 --- a/lib/chef/provider/systemd_unit.rb +++ b/lib/chef/provider/systemd_unit.rb @@ -194,7 +194,7 @@ class Chef f.group "root" f.mode "0644" f.content new_resource.to_ini - f.verify :systemd_unit + f.verify :systemd_unit if new_resource.verify end.run_action(action) end diff --git a/lib/chef/provider/template/content.rb b/lib/chef/provider/template/content.rb index acf200621d..b40794564a 100644 --- a/lib/chef/provider/template/content.rb +++ b/lib/chef/provider/template/content.rb @@ -36,7 +36,30 @@ class Chef private def file_for_provider - context = TemplateContext.new(new_resource.variables) + # Deal with any DelayedEvaluator values in the template variables. + visitor = lambda do |obj| + case obj + when Hash + # If this is an Attribute object, we need to change class otherwise + # we get the immutable behavior. This could probably be fixed by + # using Hash#transform_values once we only support Ruby 2.4. + obj_class = obj.is_a?(Chef::Node::ImmutableMash) ? Mash : obj.class + # Avoid mutating hashes in the resource in case we're changing anything. + obj.each_with_object(obj_class.new) do |(key, value), memo| + memo[key] = visitor.call(value) + end + when Array + # Avoid mutating arrays in the resource in case we're changing anything. + obj.map { |value| visitor.call(value) } + when DelayedEvaluator + new_resource.instance_eval(&obj) + else + obj + end + end + variables = visitor.call(new_resource.variables) + + context = TemplateContext.new(variables) context[:node] = run_context.node context[:template_finder] = template_finder diff --git a/lib/chef/provider/user.rb b/lib/chef/provider/user.rb index dcfee22c31..abdff441a5 100644 --- a/lib/chef/provider/user.rb +++ b/lib/chef/provider/user.rb @@ -17,13 +17,11 @@ # require "chef/provider" -require "chef/mixin/command" require "etc" class Chef class Provider class User < Chef::Provider - include Chef::Mixin::Command attr_accessor :user_exists, :locked diff --git a/lib/chef/provider/user/linux.rb b/lib/chef/provider/user/linux.rb index cf75bdc38b..2db6c218bd 100644 --- a/lib/chef/provider/user/linux.rb +++ b/lib/chef/provider/user/linux.rb @@ -58,6 +58,7 @@ class Chef def usermod_options opts = [] + opts += [ "-u", new_resource.uid ] if new_resource.non_unique if updating_home? if new_resource.manage_home opts << "-m" diff --git a/lib/chef/provider/user/pw.rb b/lib/chef/provider/user/pw.rb index cf47bb7fde..06bd221d26 100644 --- a/lib/chef/provider/user/pw.rb +++ b/lib/chef/provider/user/pw.rb @@ -94,13 +94,7 @@ class Chef if !new_resource.password.nil? && (current_resource.password != new_resource.password) Chef::Log.debug("#{new_resource} updating password") command = "pw usermod #{new_resource.username} -H 0" - status = popen4(command, waitlast: true) do |pid, stdin, stdout, stderr| - stdin.puts new_resource.password.to_s - end - - unless status.exitstatus == 0 - raise Chef::Exceptions::User, "pw failed - #{status.inspect}!" - end + shell_out!(command, input: new_resource.password.to_s) else Chef::Log.debug("#{new_resource} no change needed to password") end diff --git a/lib/chef/provider/windows_task.rb b/lib/chef/provider/windows_task.rb new file mode 100644 index 0000000000..a96d4b2b7e --- /dev/null +++ b/lib/chef/provider/windows_task.rb @@ -0,0 +1,418 @@ +# +# Author:: Nimisha Sharad (<nimisha.sharad@msystechnologies.com>) +# Copyright:: Copyright 2008-2016, Chef Software Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "chef/mixin/shell_out" +require "rexml/document" +require "iso8601" +require "chef/mixin/powershell_out" + +class Chef + class Provider + class WindowsTask < Chef::Provider + include Chef::Mixin::ShellOut + include Chef::Mixin::PowershellOut + + provides :windows_task, os: "windows" + + def load_current_resource + @current_resource = Chef::Resource::WindowsTask.new(new_resource.name) + pathed_task_name = new_resource.task_name.start_with?('\\') ? new_resource.task_name : "\\#{new_resource.task_name}" + + @current_resource.task_name(pathed_task_name) + task_hash = load_task_hash(pathed_task_name) + + set_current_resource(task_hash) if task_hash.respond_to?(:[]) && task_hash[:TaskName] == pathed_task_name + @current_resource + end + + def set_current_resource(task_hash) + @current_resource.exists = true + @current_resource.command(task_hash[:TaskToRun]) + @current_resource.cwd(task_hash[:StartIn]) unless task_hash[:StartIn] == "N/A" + @current_resource.user(task_hash[:RunAsUser]) + set_current_run_level task_hash[:run_level] + set_current_frequency task_hash + @current_resource.day(task_hash[:day]) if task_hash[:day] + @current_resource.months(task_hash[:months]) if task_hash[:months] + set_current_idle_time(task_hash[:idle_time]) if task_hash[:idle_time] + @current_resource.random_delay(task_hash[:random_delay]) if task_hash[:random_delay] + @current_resource.execution_time_limit(task_hash[:execution_time_limit]) if task_hash[:execution_time_limit] + + @current_resource.status = :running if task_hash[:Status] == "Running" + @current_resource.enabled = true if task_hash[:ScheduledTaskState] == "Enabled" + end + + def action_create + if @current_resource.exists && !(task_need_update? || @new_resource.force) + Chef::Log.info "#{@new_resource} task already exists - nothing to do" + else + options = {} + options["F"] = "" if @new_resource.force || task_need_update? + options["SC"] = schedule + options["MO"] = @new_resource.frequency_modifier if frequency_modifier_allowed + options["I"] = @new_resource.idle_time unless @new_resource.idle_time.nil? + options["SD"] = @new_resource.start_day unless @new_resource.start_day.nil? + options["ST"] = @new_resource.start_time unless @new_resource.start_time.nil? + options["TR"] = @new_resource.command + options["RU"] = @new_resource.user + options["RP"] = @new_resource.password if use_password? + options["RL"] = "HIGHEST" if @new_resource.run_level == :highest + options["IT"] = "" if @new_resource.interactive_enabled + options["D"] = @new_resource.day if @new_resource.day + options["M"] = @new_resource.months unless @new_resource.months.nil? + + run_schtasks "CREATE", options + xml_options = [] + xml_options << "cwd" if new_resource.cwd + xml_options << "random_delay" if new_resource.random_delay + xml_options << "execution_time_limit" if new_resource.execution_time_limit + update_task_xml(xml_options) unless xml_options.empty? + + new_resource.updated_by_last_action true + Chef::Log.info "#{@new_resource} task created" + end + end + + def action_run + if @current_resource.exists + if @current_resource.status == :running + Chef::Log.info "#{@new_resource} task is currently running, skipping run" + else + run_schtasks "RUN" + new_resource.updated_by_last_action true + Chef::Log.info "#{@new_resource} task ran" + end + else + Chef::Log.warn "#{@new_resource} task doesn't exists - nothing to do" + end + end + + def action_delete + if @current_resource.exists + # always need to force deletion + run_schtasks "DELETE", "F" => "" + new_resource.updated_by_last_action true + Chef::Log.info "#{@new_resource} task deleted" + else + Chef::Log.warn "#{@new_resource} task doesn't exists - nothing to do" + end + end + + def action_end + if @current_resource.exists + if @current_resource.status != :running + Chef::Log.debug "#{@new_resource} is not running - nothing to do" + else + run_schtasks "END" + @new_resource.updated_by_last_action true + Chef::Log.info "#{@new_resource} task ended" + end + else + Chef::Log.warn "#{@new_resource} task doesn't exist - nothing to do" + end + end + + def action_enable + if @current_resource.exists + if @current_resource.enabled + Chef::Log.debug "#{@new_resource} already enabled - nothing to do" + else + run_schtasks "CHANGE", "ENABLE" => "" + @new_resource.updated_by_last_action true + Chef::Log.info "#{@new_resource} task enabled" + end + else + Chef::Log.fatal "#{@new_resource} task doesn't exist - nothing to do" + raise Errno::ENOENT, "#{@new_resource}: task does not exist, cannot enable" + end + end + + def action_disable + if @current_resource.exists + if @current_resource.enabled + run_schtasks "CHANGE", "DISABLE" => "" + @new_resource.updated_by_last_action true + Chef::Log.info "#{@new_resource} task disabled" + else + Chef::Log.warn "#{@new_resource} already disabled - nothing to do" + end + else + Chef::Log.warn "#{@new_resource} task doesn't exist - nothing to do" + end + end + + private + + # rubocop:disable Style/StringLiteralsInInterpolation + def run_schtasks(task_action, options = {}) + cmd = "schtasks /#{task_action} /TN \"#{@new_resource.task_name}\" " + options.keys.each do |option| + cmd += "/#{option} " + cmd += "\"#{options[option].to_s.gsub('"', "\\\"")}\" " unless options[option] == "" + end + Chef::Log.debug("running: ") + Chef::Log.debug(" #{cmd}") + shell_out!(cmd, returns: [0]) + end + # rubocop:enable Style/StringLiteralsInInterpolation + + def task_need_update? + return true if @current_resource.command != @new_resource.command.tr("'", '"') || + @current_resource.user != @new_resource.user || + @current_resource.run_level != @new_resource.run_level || + @current_resource.cwd != @new_resource.cwd || + @current_resource.frequency_modifier != @new_resource.frequency_modifier || + @current_resource.frequency != @new_resource.frequency || + @current_resource.idle_time != @new_resource.idle_time || + @current_resource.random_delay != @new_resource.random_delay || + @current_resource.execution_time_limit != @new_resource.execution_time_limit || + !@new_resource.start_day.nil? || !@new_resource.start_time.nil? + + begin + return true if @new_resource.day.to_s.casecmp(@current_resource.day.to_s) != 0 || + @new_resource.months.to_s.casecmp(@current_resource.months.to_s) != 0 + rescue + Chef::Log.debug "caught a raise in task_needs_update?" + end + + false + end + + def update_task_xml(options = []) + # random_delay xml element is different for different frequencies + random_delay_xml_element = { + :minute => "Triggers/TimeTrigger/RandomDelay", + :hourly => "Triggers/TimeTrigger/RandomDelay", + :once => "Triggers/TimeTrigger/RandomDelay", + :daily => "Triggers/CalendarTrigger/RandomDelay", + :weekly => "Triggers/CalendarTrigger/RandomDelay", + :monthly => "Triggers/CalendarTrigger/RandomDelay", + } + + xml_element_mapping = { + "cwd" => "Actions/Exec/WorkingDirectory", + "random_delay" => random_delay_xml_element[@new_resource.frequency], + "execution_time_limit" => "Settings/ExecutionTimeLimit", + } + + Chef::Log.debug "looking for existing tasks" + + task_script = <<-EOH + [Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8 + schtasks /Query /TN \"#{@new_resource.task_name}\" /XML + EOH + xml_cmd = powershell_out(task_script) + + return if xml_cmd.exitstatus != 0 + + doc = REXML::Document.new(xml_cmd.stdout) + + options.each do |option| + Chef::Log.debug 'Removing former #{option} if any' + doc.root.elements.delete(xml_element_mapping[option]) + option_value = @new_resource.send("#{option}") + + if option_value + Chef::Log.debug "Setting #option as #option_value" + split_xml_path = xml_element_mapping[option].split("/") # eg. if xml_element_mapping[option] = "Actions/Exec/WorkingDirectory" + element_name = split_xml_path.last # element_name = "WorkingDirectory" + cwd_element = REXML::Element.new(element_name) + cwd_element.add_text(option_value) + element_root = (split_xml_path - [element_name]).join("/") # element_root = 'Actions/Exec' + exec_element = doc.root.elements[element_root] + exec_element.add_element(cwd_element) + end + end + + temp_task_file = ::File.join(ENV["TEMP"], "windows_task.xml") + begin + ::File.open(temp_task_file, "w:UTF-16LE") do |f| + doc.write(f) + end + + options = {} + options["RU"] = @new_resource.user if @new_resource.user + options["RP"] = @new_resource.password if @new_resource.password + options["IT"] = "" if @new_resource.interactive_enabled + options["XML"] = temp_task_file + + run_schtasks("DELETE", "F" => "") + run_schtasks("CREATE", options) + ensure + ::File.delete(temp_task_file) + end + end + + def load_task_hash(task_name) + Chef::Log.debug "Looking for existing tasks" + + task_script = <<-EOH + [Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8 + schtasks /Query /FO LIST /V /TN \"#{task_name}\" + EOH + + output = powershell_out(task_script).stdout.force_encoding("UTF-8") + if output.empty? + task = false + else + task = {} + + output.split("\n").map! do |line| + line.split(": ").map!(&:strip) + end.each do |field| + if field.is_a?(Array) && field[0].respond_to?(:to_sym) + key = (field - [field.last]).join(": ") + task[key.gsub(/\s+/, "").to_sym] = field.last + end + end + end + + task_xml = load_task_xml task_name + task.merge!(task_xml) if task && task_xml + + task + end + + def load_task_xml(task_name) + task_script = <<-EOH + [Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8 + schtasks /Query /TN \"#{task_name}\" /XML + EOH + xml_cmd = powershell_out(task_script) + + return if xml_cmd.exitstatus != 0 + + doc = REXML::Document.new(xml_cmd.stdout) + root = doc.root + + task = {} + task[:run_level] = root.elements["Principals/Principal/RunLevel"].text if root.elements["Principals/Principal/RunLevel"] + + # for frequency = :minutes, :hourly + task[:repetition_interval] = root.elements["Triggers/TimeTrigger/Repetition/Interval"].text if root.elements["Triggers/TimeTrigger/Repetition/Interval"] + + # for frequency = :daily + task[:schedule_by_day] = root.elements["Triggers/CalendarTrigger/ScheduleByDay/DaysInterval"].text if root.elements["Triggers/CalendarTrigger/ScheduleByDay/DaysInterval"] + + # for frequency = :weekly + task[:schedule_by_week] = root.elements["Triggers/CalendarTrigger/ScheduleByWeek/WeeksInterval"].text if root.elements["Triggers/CalendarTrigger/ScheduleByWeek/WeeksInterval"] + if root.elements["Triggers/CalendarTrigger/ScheduleByWeek/DaysOfWeek"] + task[:day] = [] + root.elements["Triggers/CalendarTrigger/ScheduleByWeek/DaysOfWeek"].elements.each do |e| + task[:day] << e.to_s[0..3].delete("<").delete("/>") + end + task[:day] = task[:day].join(", ") + end + + # for frequency = :monthly + task[:schedule_by_month] = root.elements["Triggers/CalendarTrigger/ScheduleByMonth/DaysOfMonth/Day"].text if root.elements["Triggers/CalendarTrigger/ScheduleByMonth/DaysOfMonth/Day"] + if root.elements["Triggers/CalendarTrigger/ScheduleByMonth/Months"] + task[:months] = [] + root.elements["Triggers/CalendarTrigger/ScheduleByMonth/Months"].elements.each do |e| + task[:months] << e.to_s[0..3].delete("<").delete("/>") + end + task[:months] = task[:months].join(", ") + end + + task[:on_logon] = true if root.elements["Triggers/LogonTrigger"] + task[:onstart] = true if root.elements["Triggers/BootTrigger"] + task[:on_idle] = true if root.elements["Triggers/IdleTrigger"] + + task[:idle_time] = root.elements["Settings/IdleSettings/Duration"].text if root.elements["Settings/IdleSettings/Duration"] && task[:on_idle] + + task[:once] = true if !(task[:repetition_interval] || task[:schedule_by_day] || task[:schedule_by_week] || task[:schedule_by_month] || task[:on_logon] || task[:onstart] || task[:on_idle]) + task[:execution_time_limit] = root.elements["Settings/ExecutionTimeLimit"].text if root.elements["Settings/ExecutionTimeLimit"] #by default PT72H + task[:random_delay] = root.elements["Triggers/TimeTrigger/RandomDelay"].text if root.elements["Triggers/TimeTrigger/RandomDelay"] + task[:random_delay] = root.elements["Triggers/CalendarTrigger/RandomDelay"].text if root.elements["Triggers/CalendarTrigger/RandomDelay"] + task + end + + SYSTEM_USERS = ['NT AUTHORITY\SYSTEM', "SYSTEM", 'NT AUTHORITY\LOCALSERVICE', 'NT AUTHORITY\NETWORKSERVICE', 'BUILTIN\USERS', "USERS"].freeze + + def use_password? + @use_password ||= !SYSTEM_USERS.include?(@new_resource.user.upcase) + end + + def schedule + case @new_resource.frequency + when :on_logon + "ONLOGON" + when :on_idle + "ONIDLE" + else + @new_resource.frequency + end + end + + def frequency_modifier_allowed + case @new_resource.frequency + when :minute, :hourly, :daily, :weekly + true + when :monthly + @new_resource.months.nil? || %w{ FIRST SECOND THIRD FOURTH LAST LASTDAY }.include?(@new_resource.frequency_modifier) + else + false + end + end + + def set_current_run_level(run_level) + case run_level + when "HighestAvailable" + @current_resource.run_level(:highest) + when "LeastPrivilege" + @current_resource.run_level(:limited) + end + end + + def set_current_frequency(task_hash) + if task_hash[:repetition_interval] + duration = ISO8601::Duration.new(task_hash[:repetition_interval]) + if task_hash[:repetition_interval].include?("M") + @current_resource.frequency(:minute) + @current_resource.frequency_modifier(duration.minutes.atom.to_i) + elsif task_hash[:repetition_interval].include?("H") + @current_resource.frequency(:hourly) + @current_resource.frequency_modifier(duration.hours.atom.to_i) + end + end + + if task_hash[:schedule_by_day] + @current_resource.frequency(:daily) + @current_resource.frequency_modifier(task_hash[:schedule_by_day].to_i) + end + + if task_hash[:schedule_by_week] + @current_resource.frequency(:weekly) + @current_resource.frequency_modifier(task_hash[:schedule_by_week].to_i) + end + + @current_resource.frequency(:monthly) if task_hash[:schedule_by_month] + @current_resource.frequency(:on_logon) if task_hash[:on_logon] + @current_resource.frequency(:onstart) if task_hash[:onstart] + @current_resource.frequency(:on_idle) if task_hash[:on_idle] + @current_resource.frequency(:once) if task_hash[:once] + end + + def set_current_idle_time(idle_time) + duration = ISO8601::Duration.new(idle_time) + @current_resource.idle_time(duration.minutes.atom.to_i) + end + + end + end +end diff --git a/lib/chef/provider/yum_repository.rb b/lib/chef/provider/yum_repository.rb index bcba8e676d..a5d3c2bc39 100644 --- a/lib/chef/provider/yum_repository.rb +++ b/lib/chef/provider/yum_repository.rb @@ -26,8 +26,6 @@ require "chef/provider/noop" class Chef class Provider class YumRepository < Chef::Provider - use_inline_resources - extend Chef::Mixin::Which provides :yum_repository do diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb index 0f19f56a8f..41de44a1d6 100644 --- a/lib/chef/providers.rb +++ b/lib/chef/providers.rb @@ -59,6 +59,7 @@ require "chef/provider/template" require "chef/provider/user" require "chef/provider/whyrun_safe_ruby_block" require "chef/provider/yum_repository" +require "chef/provider/windows_task" require "chef/provider/env/windows" diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index 9dc01a8d3a..ca6603c06a 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -909,17 +909,6 @@ class Chef end # - # Set whether this class was updated during an action. - # - # @deprecated Multiple actions are supported by resources. Please call {}#updated_by_last_action} instead. - # - def updated=(true_or_false) - Chef.deprecated(:custom_resource, "Chef::Resource#updated=(true|false) is deprecated. Please call #updated_by_last_action(true|false) instead.") - updated_by_last_action(true_or_false) - @updated = true_or_false - end - - # # The display name of this resource type, for printing purposes. # # Will be used to print out the resource in messages, e.g. resource_name[name] @@ -1499,27 +1488,6 @@ class Chef self.class.resource_for_node(name, node).new("name", run_context).provider_for_action(action).class end - module DeprecatedLWRPClass - - # @api private - def register_deprecated_lwrp_class(resource_class, class_name) - if Chef::Resource.const_defined?(class_name, false) - Chef::Log.warn "#{class_name} already exists! Deprecation class overwrites #{resource_class}" - Chef::Resource.send(:remove_const, class_name) - end - - if !Chef::Config[:treat_deprecation_warnings_as_errors] - Chef::Resource.const_set(class_name, resource_class) - Chef::Resource.deprecated_constants[class_name.to_sym] = resource_class - end - end - - def deprecated_constants - raise "Deprecated constants should be called only on Chef::Resource" unless self == Chef::Resource - @deprecated_constants ||= {} - end - end - def self.remove_canonical_dsl if @resource_name remaining = Chef.resource_handler_map.delete_canonical(@resource_name, self) @@ -1528,7 +1496,6 @@ class Chef end end end - extend DeprecatedLWRPClass end end diff --git a/lib/chef/resource/action_class.rb b/lib/chef/resource/action_class.rb index d2d74b47e2..dce3be3244 100644 --- a/lib/chef/resource/action_class.rb +++ b/lib/chef/resource/action_class.rb @@ -64,8 +64,6 @@ class Chef @current_resource = current_resource end - use_inline_resources - # XXX: remove in Chef-14 def self.include_resource_dsl? true diff --git a/lib/chef/resource/chocolatey_package.rb b/lib/chef/resource/chocolatey_package.rb index 5460661f6d..a443b9a1d7 100644 --- a/lib/chef/resource/chocolatey_package.rb +++ b/lib/chef/resource/chocolatey_package.rb @@ -1,6 +1,6 @@ # # Author:: Adam Jacob (<adam@chef.io>) -# Copyright:: Copyright 2008-2016, Chef Software, Inc. +# Copyright:: Copyright 2008-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,6 +31,9 @@ class Chef @resource_name = :chocolatey_package end + # windows can't take Array options yet + property :options, String + property :package_name, [String, Array], coerce: proc { |x| [x].flatten } property :version, [String, Array], coerce: proc { |x| [x].flatten } diff --git a/lib/chef/resource/dnf_package.rb b/lib/chef/resource/dnf_package.rb index f10c282f19..d92dc12ec7 100644 --- a/lib/chef/resource/dnf_package.rb +++ b/lib/chef/resource/dnf_package.rb @@ -29,7 +29,7 @@ class Chef allowed_actions :install, :upgrade, :remove, :purge, :reconfig, :lock, :unlock, :flush_cache - provides :package, platform_family: %w{rhel fedora} do + provides :package, platform_family: %w{rhel fedora amazon} do which("dnf") && shell_out("rpm -q dnf").stdout =~ /^dnf-[1-9]/ end diff --git a/lib/chef/resource/execute.rb b/lib/chef/resource/execute.rb index 659fa341b5..dee497e74f 100644 --- a/lib/chef/resource/execute.rb +++ b/lib/chef/resource/execute.rb @@ -42,7 +42,6 @@ class Chef @cwd = nil @environment = nil @group = nil - @path = nil @returns = 0 @timeout = nil @user = nil diff --git a/lib/chef/resource/gem_package.rb b/lib/chef/resource/gem_package.rb index 5511d3c580..fc162a6033 100644 --- a/lib/chef/resource/gem_package.rb +++ b/lib/chef/resource/gem_package.rb @@ -1,6 +1,6 @@ # # Author:: Adam Jacob (<adam@chef.io>) -# Copyright:: Copyright 2008-2016, Chef Software Inc. +# Copyright:: Copyright 2008-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,11 +23,25 @@ class Chef class GemPackage < Chef::Resource::Package resource_name :gem_package + # the source can either be a path to a package source like: + # source /var/tmp/mygem-1.2.3.4.gem + # or it can be a url rubygems source like: + # https://www.rubygems.org + # the default has to be nil in order for the magical wiring up of the name property to + # the source pathname to work correctly. + # + # we don't do coercions here because its all a bit too complicated + # + # FIXME? the array form of installing paths most likely does not work? + # property :source, [ String, Array ] property :clear_sources, [ true, false ], default: false, desired_state: false # Sets a custom gem_binary to run for gem commands. property :gem_binary, String, desired_state: false + # set to false to avoid including Chef::Config[:rubygems_url] in the sources + property :include_default_source, [ TrueClass, FalseClass ], default: true + ## # Options for the gem install, either a Hash or a String. When a hash is # given, the options are passed to Gem::DependencyInstaller.new, and the diff --git a/lib/chef/resource/launchd.rb b/lib/chef/resource/launchd.rb index c78ffa3f0e..3fba76e220 100644 --- a/lib/chef/resource/launchd.rb +++ b/lib/chef/resource/launchd.rb @@ -27,7 +27,7 @@ class Chef identity_attr :label default_action :create - allowed_actions :create, :create_if_missing, :delete, :enable, :disable + allowed_actions :create, :create_if_missing, :delete, :enable, :disable, :restart property :label, String, default: lazy { name }, identity: true property :backup, [Integer, FalseClass] @@ -114,7 +114,7 @@ class Chef property :ld_group, String property :limit_load_from_hosts, Array property :limit_load_to_hosts, Array - property :limit_load_to_session_type, Array + property :limit_load_to_session_type, [ Array, String ] property :low_priority_io, [ TrueClass, FalseClass ] property :mach_services, Hash property :nice, Integer @@ -139,18 +139,6 @@ class Chef property :wait_for_debugger, [ TrueClass, FalseClass ] property :watch_paths, Array property :working_directory, String - - # hash is an instance method on Object and needs to return a Fixnum. - def hash(arg = nil) - Chef.deprecated(:launchd_hash_property, "Property `hash` on the `launchd` resource has changed to `plist_hash`." \ - "Please use `plist_hash` instead. This will raise an exception in Chef 13.") - - set_or_return( - :plist_hash, - arg, - :kind_of => Hash - ) - end end end end diff --git a/lib/chef/resource/lwrp_base.rb b/lib/chef/resource/lwrp_base.rb index 7dfe147341..b12ac98673 100644 --- a/lib/chef/resource/lwrp_base.rb +++ b/lib/chef/resource/lwrp_base.rb @@ -69,8 +69,6 @@ class Chef LWRPBase.loaded_lwrps[filename] = true - # Create the deprecated Chef::Resource::LwrpFoo class - Chef::Resource.register_deprecated_lwrp_class(resource_class, convert_to_class_name(resource_name)) resource_class end diff --git a/lib/chef/resource/mount.rb b/lib/chef/resource/mount.rb index 2aca8432dd..7e601b861a 100644 --- a/lib/chef/resource/mount.rb +++ b/lib/chef/resource/mount.rb @@ -31,7 +31,7 @@ class Chef allowed_actions :mount, :umount, :unmount, :remount, :enable, :disable # this is a poor API please do not re-use this pattern - property :supports, Hash, default: { remount: false }, + property :supports, Hash, default: lazy { { remount: false } }, coerce: proc { |x| x.is_a?(Array) ? x.each_with_object({}) { |i, m| m[i] = true } : x } def initialize(name, run_context = nil) diff --git a/lib/chef/resource/package.rb b/lib/chef/resource/package.rb index a1f174a6f3..5647f203d2 100644 --- a/lib/chef/resource/package.rb +++ b/lib/chef/resource/package.rb @@ -36,7 +36,7 @@ class Chef property :package_name, [ String, Array ], identity: true property :version, [ String, Array ] - property :options, [ String, Array ] + property :options, [ String, Array ], coerce: proc { |x| x.is_a?(String) ? x.shellsplit : x } property :response_file, String, desired_state: false property :response_file_variables, Hash, default: lazy { {} }, desired_state: false property :source, String, desired_state: false diff --git a/lib/chef/resource/systemd_unit.rb b/lib/chef/resource/systemd_unit.rb index 688f2e9dcd..1a19a7d682 100644 --- a/lib/chef/resource/systemd_unit.rb +++ b/lib/chef/resource/systemd_unit.rb @@ -33,14 +33,19 @@ class Chef :try_restart, :reload_or_restart, :reload_or_try_restart + # Internal provider-managed properties property :enabled, [TrueClass, FalseClass] property :active, [TrueClass, FalseClass] property :masked, [TrueClass, FalseClass] property :static, [TrueClass, FalseClass] + + # User-provided properties property :user, String, desired_state: false property :content, [String, Hash] property :triggers_reload, [TrueClass, FalseClass], default: true, desired_state: false + property :verify, [TrueClass, FalseClass], + default: true, desired_state: false def to_ini case content diff --git a/lib/chef/resource/windows_package.rb b/lib/chef/resource/windows_package.rb index 0e8dd39672..e37bad4b0a 100644 --- a/lib/chef/resource/windows_package.rb +++ b/lib/chef/resource/windows_package.rb @@ -1,6 +1,6 @@ # # Author:: Bryan McLellan <btm@loftninjas.org> -# Copyright:: Copyright 2014-2016, Chef Software, Inc. +# Copyright:: Copyright 2014-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,6 +37,9 @@ class Chef @source ||= source(@package_name) if @package_name.downcase.end_with?(".msi") end + # windows can't take array options yet + property :options, String + # Unique to this resource property :installer_type, Symbol property :timeout, [ String, Integer ], default: 600 diff --git a/lib/chef/resource/windows_task.rb b/lib/chef/resource/windows_task.rb new file mode 100644 index 0000000000..25e76f4220 --- /dev/null +++ b/lib/chef/resource/windows_task.rb @@ -0,0 +1,237 @@ +# +# Author:: Nimisha Sharad (<nimisha.sharad@msystechnologies.com>) +# Copyright:: Copyright 2008-2016, Chef Software Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "chef/resource" + +class Chef + class Resource + class WindowsTask < Chef::Resource + + provides :windows_task, os: "windows" + + allowed_actions :create, :delete, :run, :end, :change, :enable, :disable + default_action :create + + def initialize(name, run_context = nil) + super + @resource_name = :windows_task + @task_name = name + @action = :create + end + + property :task_name, String, regex: [/\A[^\/\:\*\?\<\>\|]+\z/] + property :command, String + property :cwd, String + property :user, String, default: "SYSTEM" + property :password, String + property :run_level, equal_to: [:highest, :limited], default: :limited + property :force, [TrueClass, FalseClass], default: false + property :interactive_enabled, [TrueClass, FalseClass], default: false + property :frequency_modifier, [Integer, String], default: 1 + property :frequency, equal_to: [:minute, + :hourly, + :daily, + :weekly, + :monthly, + :once, + :on_logon, + :onstart, + :on_idle], default: :hourly + property :start_day, String + property :start_time, String + property :day, [String, Integer] + property :months, String + property :idle_time, Integer + property :random_delay, String + property :execution_time_limit, String + + attr_accessor :exists, :status, :enabled + + def after_created + if random_delay + validate_random_delay(random_delay, frequency) + duration = sec_to_dur(random_delay) + random_delay(duration) + end + + if execution_time_limit + raise ArgumentError, "Invalid value passed for `execution_time_limit`. Please pass seconds as a String e.g. '60'." if execution_time_limit.to_i == 0 + duration = sec_to_dur(execution_time_limit) + execution_time_limit(duration) + else + # schtask sets execution_time_limit as PT72H by default + # We are setting the default value here so that we can do idempotency check later + # Note: We can't use `default` in the property + # because it will raise error for Invalid values passed as "PT72H" is not in seconds + execution_time_limit("PT72H") + end + + validate_start_time(start_time) if frequency == :once + validate_start_day(start_day, frequency) if start_day + validate_user_and_password(user, password) + validate_interactive_setting(interactive_enabled, password) + validate_create_frequency_modifier(frequency, frequency_modifier) + validate_create_day(day, frequency) if day + validate_create_months(months, frequency) if months + validate_idle_time(idle_time, frequency) if idle_time + end + + private + + def validate_random_delay(random_delay, frequency) + if [:once, :on_logon, :onstart, :on_idle].include? frequency + raise ArgumentError, "`random_delay` property is supported only for frequency :minute, :hourly, :daily, :weekly and :monthly" + end + + raise ArgumentError, "Invalid value passed for `random_delay`. Please pass seconds as a String e.g. '60'." if random_delay.to_i == 0 + end + + def validate_start_day(start_day, frequency) + if [:once, :on_logon, :onstart, :on_idle].include? frequency + raise ArgumentError, "`start_day` property is not supported with frequency: #{frequency}" + end + end + + def validate_start_time(start_time) + raise ArgumentError, "`start_time` needs to be provided with `frequency :once`" unless start_time + end + + SYSTEM_USERS = ['NT AUTHORITY\SYSTEM', "SYSTEM", 'NT AUTHORITY\LOCALSERVICE', 'NT AUTHORITY\NETWORKSERVICE', 'BUILTIN\USERS', "USERS"].freeze + + def validate_user_and_password(user, password) + if user && use_password?(user) + if password.nil? + raise ArgumentError, "Can't specify a non-system user without a password!" + end + end + end + + def use_password?(user) + @use_password ||= !SYSTEM_USERS.include?(user.upcase) + end + + def validate_interactive_setting(interactive_enabled, password) + if interactive_enabled && password.nil? + raise ArgumentError, "Please provide the password when attempting to set interactive/non-interactive." + end + end + + def validate_create_frequency_modifier(frequency, frequency_modifier) + # Currently is handled in create action 'frequency_modifier_allowed' line. Does not allow for frequency_modifier for once,onstart,onlogon,onidle + # Note that 'OnEvent' is not a supported frequency. + unless frequency.nil? || frequency_modifier.nil? + case frequency + when :minute + unless frequency_modifier.to_i > 0 && frequency_modifier.to_i <= 1439 + raise ArgumentError, "frequency_modifier value #{frequency_modifier} is invalid. Valid values for :minute frequency are 1 - 1439." + end + when :hourly + unless frequency_modifier.to_i > 0 && frequency_modifier.to_i <= 23 + raise ArgumentError, "frequency_modifier value #{frequency_modifier} is invalid. Valid values for :hourly frequency are 1 - 23." + end + when :daily + unless frequency_modifier.to_i > 0 && frequency_modifier.to_i <= 365 + raise ArgumentError, "frequency_modifier value #{frequency_modifier} is invalid. Valid values for :daily frequency are 1 - 365." + end + when :weekly + unless frequency_modifier.to_i > 0 && frequency_modifier.to_i <= 52 + raise ArgumentError, "frequency_modifier value #{frequency_modifier} is invalid. Valid values for :weekly frequency are 1 - 52." + end + when :monthly + unless ("1".."12").to_a.push("FIRST", "SECOND", "THIRD", "FOURTH", "LAST", "LASTDAY").include?(frequency_modifier.to_s.upcase) + raise ArgumentError, "frequency_modifier value #{frequency_modifier} is invalid. Valid values for :monthly frequency are 1 - 12, 'FIRST', 'SECOND', 'THIRD', 'FOURTH', 'LAST', 'LASTDAY'." + end + end + end + end + + def validate_create_day(day, frequency) + unless [:weekly].include?(frequency) + raise "day attribute is only valid for tasks that run weekly" + end + if day.is_a?(String) && day.to_i.to_s != day + days = day.split(",") + days.each do |d| + unless ["mon", "tue", "wed", "thu", "fri", "sat", "sun", "*"].include?(d.strip.downcase) + raise "day attribute invalid. Only valid values are: MON, TUE, WED, THU, FRI, SAT, SUN and *. Multiple values must be separated by a comma." + end + end + end + end + + def validate_create_months(months, frequency) + unless [:monthly].include?(frequency) + raise "months attribute is only valid for tasks that run monthly" + end + if months.is_a? String + months.split(",").each do |month| + unless ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC", "*"].include?(month.strip.upcase) + raise "months attribute invalid. Only valid values are: JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC and *. Multiple values must be separated by a comma." + end + end + end + end + + def validate_idle_time(idle_time, frequency) + unless [:on_idle].include?(frequency) + raise "idle_time attribute is only valid for tasks that run on_idle" + end + + unless idle_time.to_i > 0 && idle_time.to_i <= 999 + raise "idle_time value #{idle_time} is invalid. Valid values for :on_idle frequency are 1 - 999." + end + end + + # Convert the number of seconds to an ISO8601 duration format + # @see http://tools.ietf.org/html/rfc2445#section-4.3.6 + # @param [Integer] seconds The amount of seconds for this duration + def sec_to_dur(seconds) + seconds = seconds.to_i + return if seconds == 0 + iso_str = "P" + if seconds > 604_800 # more than a week + weeks = seconds / 604_800 + seconds -= (604_800 * weeks) + iso_str << "#{weeks}W" + end + if seconds > 86_400 # more than a day + days = seconds / 86_400 + seconds -= (86_400 * days) + iso_str << "#{days}D" + end + if seconds > 0 + iso_str << "T" + if seconds > 3600 # more than an hour + hours = seconds / 3600 + seconds -= (3600 * hours) + iso_str << "#{hours}H" + end + if seconds > 60 # more than a minute + minutes = seconds / 60 + seconds -= (60 * minutes) + iso_str << "#{minutes}M" + end + iso_str << "#{seconds}S" + end + + iso_str + end + + end + end +end diff --git a/lib/chef/resource/yum_package.rb b/lib/chef/resource/yum_package.rb index 2fc5db5cc3..078725e306 100644 --- a/lib/chef/resource/yum_package.rb +++ b/lib/chef/resource/yum_package.rb @@ -22,7 +22,7 @@ class Chef class Resource class YumPackage < Chef::Resource::Package resource_name :yum_package - provides :package, os: "linux", platform_family: %w{rhel fedora} + provides :package, os: "linux", platform_family: %w{rhel fedora amazon} # XXX: the coercions here are due to the provider promiscuously updating the properties on the # new_resource which causes immutable modification exceptions when passed an immutable node array. diff --git a/lib/chef/resource/zypper_package.rb b/lib/chef/resource/zypper_package.rb index f9e3eef49e..6c6e308159 100644 --- a/lib/chef/resource/zypper_package.rb +++ b/lib/chef/resource/zypper_package.rb @@ -23,6 +23,8 @@ class Chef class ZypperPackage < Chef::Resource::Package resource_name :zypper_package provides :package, platform_family: "suse" + + property :gpg_check, [ TrueClass, FalseClass ], default: lazy { Chef::Config[:zypper_check_gpg] } end end end diff --git a/lib/chef/resource_collection/resource_set.rb b/lib/chef/resource_collection/resource_set.rb index 111d23dc09..6ff29247a0 100644 --- a/lib/chef/resource_collection/resource_set.rb +++ b/lib/chef/resource_collection/resource_set.rb @@ -161,27 +161,39 @@ class Chef end def find_resource_by_string(arg) - results = Array.new - case arg - when MULTIPLE_RESOURCE_MATCH - resource_type = $1 - arg =~ /^.+\[(.+)\]$/ - resource_list = $1 - resource_list.split(",").each do |instance_name| - results << lookup(create_key(resource_type, instance_name)) - end - when SINGLE_RESOURCE_MATCH + begin + if arg =~ SINGLE_RESOURCE_MATCH resource_type = $1 name = $2 - results << lookup(create_key(resource_type, name)) - when NAMELESS_RESOURCE_MATCH - resource_type = $1 - name = "" - results << lookup(create_key(resource_type, name)) + return [ lookup(create_key(resource_type, name)) ] + end + rescue Chef::Exceptions::ResourceNotFound => e + if arg =~ MULTIPLE_RESOURCE_MATCH + begin + results = Array.new + resource_type = $1 + arg =~ /^.+\[(.+)\]$/ + resource_list = $1 + resource_list.split(",").each do |instance_name| + results << lookup(create_key(resource_type, instance_name)) + end + Chef.deprecated(:multiresource_match, "The resource_collection multi-resource syntax is deprecated") + return results + rescue Chef::Exceptions::ResourceNotFound + raise e + end else - raise ArgumentError, "Bad string format #{arg}, you must have a string like resource_type[name]!" + raise e + end end - results + + if arg =~ NAMELESS_RESOURCE_MATCH + resource_type = $1 + name = "" + return [ lookup(create_key(resource_type, name)) ] + end + + raise ArgumentError, "Bad string format #{arg}, you must have a string like resource_type[name]!" end end end diff --git a/lib/chef/resource_resolver.rb b/lib/chef/resource_resolver.rb index 10b8c0f22e..a5c44f65b3 100644 --- a/lib/chef/resource_resolver.rb +++ b/lib/chef/resource_resolver.rb @@ -54,11 +54,6 @@ class Chef # @api private attr_reader :resource_name # @api private - def resource - Chef.deprecated(:custom_resource, "Chef::ResourceResolver.resource deprecated. Use resource_name instead.") - resource_name - end - # @api private attr_reader :action # @api private attr_reader :canonical diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb index a254fa601f..9f87cb2454 100644 --- a/lib/chef/resources.rb +++ b/lib/chef/resources.rb @@ -98,3 +98,4 @@ require "chef/resource/zypper_package" require "chef/resource/cab_package" require "chef/resource/powershell_package" require "chef/resource/msu_package" +require "chef/resource/windows_task" diff --git a/lib/chef/run_context/cookbook_compiler.rb b/lib/chef/run_context/cookbook_compiler.rb index b2a8d236a3..f2598a4f27 100644 --- a/lib/chef/run_context/cookbook_compiler.rb +++ b/lib/chef/run_context/cookbook_compiler.rb @@ -57,6 +57,7 @@ class Chef # Run the compile phase of the chef run. Loads files in the following order: # * Libraries + # * Ohai # * Attributes # * LWRPs # * Resource Definitions @@ -69,6 +70,7 @@ class Chef # #cookbook_order for more information. def compile compile_libraries + compile_ohai_plugins compile_attributes compile_lwrps compile_resource_definitions @@ -101,11 +103,31 @@ class Chef @events.library_load_complete end + # Loads Ohai Plugins from cookbooks, and ensure any old ones are + # properly cleaned out + def compile_ohai_plugins + ohai_plugin_count = count_files_by_segment(:ohai) + @events.ohai_plugin_load_start(ohai_plugin_count) + FileUtils.rm_rf(Chef::Config[:ohai_segment_plugin_path]) + + cookbook_order.each do |cookbook| + load_ohai_plugins_from_cookbook(cookbook) + end + + # Doing a full ohai system check is costly, so only do so if we've loaded additional plugins + if ohai_plugin_count > 0 + ohai = Ohai::System.new.run_additional_plugins(Chef::Config[:ohai_segment_plugin_path]) + node.consume_ohai_data(ohai) + end + + @events.ohai_plugin_load_complete + end + # Loads attributes files from cookbooks. Attributes files are loaded # according to #cookbook_order; within a cookbook, +default.rb+ is loaded # first, then the remaining attributes files in lexical sort order. def compile_attributes - @events.attribute_load_start(count_files_by_segment(:attributes)) + @events.attribute_load_start(count_files_by_segment(:attributes, "attributes.rb")) cookbook_order.each do |cookbook| load_attributes_from_cookbook(cookbook) end @@ -166,7 +188,16 @@ class Chef def load_attributes_from_cookbook(cookbook_name) list_of_attr_files = files_in_cookbook_by_segment(cookbook_name, :attributes).dup - if default_file = list_of_attr_files.find { |path| File.basename(path) == "default.rb" } + root_alias = cookbook_collection[cookbook_name].files_for(:root_files).find { |record| record[:name] == "attributes.rb" } + default_file = list_of_attr_files.find { |path| File.basename(path) == "default.rb" } + if root_alias + if default_file + Chef::Log.error("Cookbook #{cookbook_name} contains both attributes.rb and and attributes/default.rb, ignoring attributes/default.rb") + list_of_attr_files.delete(default_file) + end + # The actual root_alias path decoding is handled in CookbookVersion#attribute_filenames_by_short_filename + load_attribute_file(cookbook_name.to_s, "default") + elsif default_file list_of_attr_files.delete(default_file) load_attribute_file(cookbook_name.to_s, default_file) end @@ -226,6 +257,19 @@ class Chef raise end + def load_ohai_plugins_from_cookbook(cookbook_name) + target = Chef::Config[:ohai_segment_plugin_path] + files_in_cookbook_by_segment(cookbook_name, :ohai).each do |filename| + next unless File.extname(filename) == ".rb" + + Chef::Log.debug "Loading Ohai plugin: #{filename} from #{cookbook_name}" + target_name = File.join(target, cookbook_name.to_s, File.basename(filename)) + + FileUtils.mkdir_p(File.dirname(target_name)) + FileUtils.cp(filename, target_name) + end + end + def load_resource_definitions_from_cookbook(cookbook_name) files_in_cookbook_by_segment(cookbook_name, :definitions).each do |filename| begin @@ -259,16 +303,16 @@ class Chef ordered_cookbooks << cookbook end - def count_files_by_segment(segment) + def count_files_by_segment(segment, root_alias = nil) cookbook_collection.inject(0) do |count, cookbook_by_name| - count + cookbook_by_name[1].segment_filenames(segment).size + count + cookbook_by_name[1].segment_filenames(segment).size + (root_alias ? cookbook_by_name[1].files_for(:root_files).select { |record| record[:name] == root_alias }.size : 0) end end # Lists the local paths to files in +cookbook+ of type +segment+ # (attribute, recipe, etc.), sorted lexically. def files_in_cookbook_by_segment(cookbook, segment) - cookbook_collection[cookbook].segment_filenames(segment).sort + cookbook_collection[cookbook].files_for(segment).map { |record| record[:full_path] }.sort end # Yields the name, as a symbol, of each cookbook depended on by diff --git a/lib/chef/search/query.rb b/lib/chef/search/query.rb index 7357dbf6be..6f494819ba 100644 --- a/lib/chef/search/query.rb +++ b/lib/chef/search/query.rb @@ -1,6 +1,6 @@ # # Author:: Adam Jacob (<adam@chef.io>) -# Copyright:: Copyright 2008-2016, Chef Software Inc. +# Copyright:: Copyright 2008-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -63,6 +63,14 @@ class Chef validate_type(type) args_h = hashify_args(*args) + if args_h[:fuzz] + if type == :node + query = fuzzify_node_query(query) + end + # FIXME: can i haz proper ruby-2.x named parameters someday plz? + args_h = args_h.reject { |k, v| k == :fuzz } + end + response = call_rest_service(type, query: query, **args_h) if block @@ -92,6 +100,14 @@ class Chef private + def fuzzify_node_query(query) + if query !~ /:/ + "tags:*#{query}* OR roles:*#{query}* OR fqdn:*#{query}* OR addresses:*#{query}* OR policy_name:*#{query}* OR policy_group:*#{query}*" + else + query + end + end + def validate_type(t) unless t.kind_of?(String) || t.kind_of?(Symbol) msg = "Invalid search object type #{t.inspect} (#{t.class}), must be a String or Symbol." + diff --git a/lib/chef/version.rb b/lib/chef/version.rb index f509cda66c..098751072f 100644 --- a/lib/chef/version.rb +++ b/lib/chef/version.rb @@ -21,7 +21,7 @@ class Chef CHEF_ROOT = File.expand_path("../..", __FILE__) - VERSION = "13.0.79" + VERSION = "13.0.118" end # diff --git a/omnibus/Gemfile.lock b/omnibus/Gemfile.lock index 32cdf871f2..3d32a97a15 100644 --- a/omnibus/Gemfile.lock +++ b/omnibus/Gemfile.lock @@ -1,14 +1,14 @@ GIT remote: https://github.com/chef/license_scout - revision: 2cf81860f92d4f2df4444341048b8aeec2da0cfa + revision: ff3cb28159e72414d63008f9a0d42e85d4aec4ba specs: - license_scout (0.1.2) + license_scout (0.1.3) ffi-yajl (~> 2.2) mixlib-shellout (~> 2.2) GIT remote: https://github.com/chef/omnibus - revision: 433220e0b7c434dbc4a36daaa1fecbdb1bf7231d + revision: ffbda9ad7d37ddb485342505ff4407c6ff23d95f specs: omnibus (5.5.0) aws-sdk (~> 2) @@ -25,7 +25,7 @@ GIT GIT remote: https://github.com/chef/omnibus-software - revision: bc08deef668865990bde3a0f0dc1ef3c3f5be735 + revision: cae44c1a3ebf7207516813ba15b372231c253954 specs: omnibus-software (4.0.0) chef-sugar (>= 3.4.0) @@ -38,13 +38,13 @@ GEM public_suffix (~> 2.0, >= 2.0.2) artifactory (2.8.1) awesome_print (1.7.0) - aws-sdk (2.8.14) - aws-sdk-resources (= 2.8.14) - aws-sdk-core (2.8.14) + aws-sdk (2.9.7) + aws-sdk-resources (= 2.9.7) + aws-sdk-core (2.9.7) aws-sigv4 (~> 1.0) jmespath (~> 1.0) - aws-sdk-resources (2.8.14) - aws-sdk-core (= 2.8.14) + aws-sdk-resources (2.9.7) + aws-sdk-core (= 2.9.7) aws-sigv4 (1.0.0) berkshelf (4.3.5) addressable (~> 2.3, >= 2.3.4) @@ -113,12 +113,12 @@ GEM iostruct (0.0.4) ipaddress (0.8.3) jmespath (1.3.1) - json (2.0.3) + json (2.0.4) kitchen-vagrant (0.19.0) test-kitchen (~> 1.4) libyajl2 (1.2.0) little-plugger (1.1.4) - logging (2.2.0) + logging (2.2.2) little-plugger (~> 1.1) multi_json (~> 1.10) method_source (0.8.2) @@ -150,7 +150,7 @@ GEM net-ssh (>= 2.6.5) nio4r (2.0.0) nori (2.6.0) - octokit (4.6.2) + octokit (4.7.0) sawyer (~> 0.8.0, >= 0.5.3) ohai (8.23.0) chef-config (>= 12.5.0.alpha.1, < 13) @@ -231,7 +231,7 @@ GEM hashie (>= 2.0.2, < 4.0.0) win32-process (0.8.3) ffi (>= 1.0.0) - winrm (2.1.3) + winrm (2.2.1) builder (>= 2.1.2) erubis (~> 2.7) gssapi (~> 1.2) diff --git a/omnibus/config/software/chef-gem-inspec.rb b/omnibus/config/software/chef-gem-inspec.rb new file mode 100644 index 0000000000..8c5e1cbf26 --- /dev/null +++ b/omnibus/config/software/chef-gem-inspec.rb @@ -0,0 +1,10 @@ +# gem installs this gem from the version specified in chef's Gemfile.lock +# so we can take advantage of omnibus's caching. Just duplicate this file and +# add the new software def to chef software def if you want to separate +# another gem's installation. +require_relative "../../files/chef-gem/build-chef-gem/gem-install-software-def" +BuildChefGem::GemInstallSoftwareDef.define(self, __FILE__) + +license "Apache-2.0" +license_file "https://raw.githubusercontent.com/chef/inspec/master/LICENSE" +skip_transitive_dependency_licensing true diff --git a/omnibus/config/software/chef.rb b/omnibus/config/software/chef.rb index c0af403b20..4726f8b687 100644 --- a/omnibus/config/software/chef.rb +++ b/omnibus/config/software/chef.rb @@ -26,7 +26,6 @@ end # For nokogiri dependency "libxml2" dependency "libxslt" -dependency "libiconv" dependency "liblzma" dependency "zlib" @@ -45,6 +44,7 @@ dependency "chef-gem-ruby-prof" dependency "chef-gem-byebug" dependency "chef-gem-debug_inspector" dependency "chef-gem-binding_of_caller" +dependency "chef-gem-inspec" unless ios_xr? || solaris? dependency "chef-gem-rbnacl-libsodium" dependency "chef-gem-bcrypt_pbkdf-ruby" diff --git a/omnibus/files/chef-gem/build-chef-gem.rb b/omnibus/files/chef-gem/build-chef-gem.rb index 9e5bf9b996..c9aaaada1d 100644 --- a/omnibus/files/chef-gem/build-chef-gem.rb +++ b/omnibus/files/chef-gem/build-chef-gem.rb @@ -99,7 +99,7 @@ module BuildChefGem --with-xml2-include=#{Shellwords.escape("#{install_dir}/embedded/include/libxml2")} --with-xslt-lib=#{Shellwords.escape("#{install_dir}/embedded/lib")} --with-xslt-include=#{Shellwords.escape("#{install_dir}/embedded/include/libxslt")} - --with-iconv-dir=#{Shellwords.escape("#{install_dir}/embedded")} + --without-iconv-dir --with-zlib-dir=#{Shellwords.escape("#{install_dir}/embedded")} }.join(" "), } diff --git a/omnibus_overrides.rb b/omnibus_overrides.rb index 32f460499c..0409c2a1bc 100644 --- a/omnibus_overrides.rb +++ b/omnibus_overrides.rb @@ -3,7 +3,7 @@ override :rubygems, version: "2.6.11" override :bundler, version: "1.12.5" override "libffi", version: "3.2.1" override "libiconv", version: "1.14" -override "liblzma", version: "5.2.2" +override "liblzma", version: "5.2.3" override "libtool", version: "2.4.2" override "libxml2", version: "2.9.4" override "libxslt", version: "1.1.29" diff --git a/spec/data/cookbooks/openldap/templates/default/openldap_nested_variable_stuff.erb b/spec/data/cookbooks/openldap/templates/default/openldap_nested_variable_stuff.erb new file mode 100644 index 0000000000..5ebee33806 --- /dev/null +++ b/spec/data/cookbooks/openldap/templates/default/openldap_nested_variable_stuff.erb @@ -0,0 +1 @@ +super secret is <%= @secret.first["key"] -%> diff --git a/spec/data/lwrp/providers/buck_passer.rb b/spec/data/lwrp/providers/buck_passer.rb index 2bbca07bf7..0bbd413867 100644 --- a/spec/data/lwrp/providers/buck_passer.rb +++ b/spec/data/lwrp/providers/buck_passer.rb @@ -10,7 +10,7 @@ def without_deprecation_warnings(&block) end end -action :pass_buck do +def action_pass_buck lwrp_foo :prepared_thumbs do action :prepare_thumbs # We know there will be a deprecation error here; head it off diff --git a/spec/data/lwrp/providers/buck_passer_2.rb b/spec/data/lwrp/providers/buck_passer_2.rb index c3bab7266f..980506c671 100644 --- a/spec/data/lwrp/providers/buck_passer_2.rb +++ b/spec/data/lwrp/providers/buck_passer_2.rb @@ -8,7 +8,7 @@ def without_deprecation_warnings(&block) end end -action :pass_buck do +def action_pass_buck lwrp_bar :prepared_eyes do action :prepare_eyes # We know there will be a deprecation error here; head it off diff --git a/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb b/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb index 77c1111ff5..d6996da55e 100644 --- a/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb +++ b/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb @@ -13,7 +13,7 @@ def without_deprecation_warnings(&block) end end -action :twiddle_thumbs do +def action_twiddle_thumbs @enclosed_resource = lwrp_foo :foo do monkey generate_new_name(new_resource.monkey){ 'the monkey' } # We know there will be a deprecation error here; head it off diff --git a/spec/data/lwrp/providers/inline_compiler.rb b/spec/data/lwrp/providers/inline_compiler.rb index 2535276b24..91a80b32af 100644 --- a/spec/data/lwrp/providers/inline_compiler.rb +++ b/spec/data/lwrp/providers/inline_compiler.rb @@ -1,6 +1,4 @@ -use_inline_resources - action :test do ruby_block "interior-ruby-block-1" do diff --git a/spec/data/root_alias_cookbooks/dup_attr/attributes.rb b/spec/data/root_alias_cookbooks/dup_attr/attributes.rb new file mode 100644 index 0000000000..3a3bab96e1 --- /dev/null +++ b/spec/data/root_alias_cookbooks/dup_attr/attributes.rb @@ -0,0 +1 @@ +default["aliased"]["attr"] = "value" diff --git a/spec/data/root_alias_cookbooks/dup_attr/attributes/default.rb b/spec/data/root_alias_cookbooks/dup_attr/attributes/default.rb new file mode 100644 index 0000000000..a6f6c78bb0 --- /dev/null +++ b/spec/data/root_alias_cookbooks/dup_attr/attributes/default.rb @@ -0,0 +1 @@ +default["aliased"]["attr"] = "other" diff --git a/spec/data/root_alias_cookbooks/dup_attr/metadata.rb b/spec/data/root_alias_cookbooks/dup_attr/metadata.rb new file mode 100644 index 0000000000..703a73ab19 --- /dev/null +++ b/spec/data/root_alias_cookbooks/dup_attr/metadata.rb @@ -0,0 +1,2 @@ +name "dup_attr" +version "1.0.0" diff --git a/spec/data/root_alias_cookbooks/dup_attr/recipe.rb b/spec/data/root_alias_cookbooks/dup_attr/recipe.rb new file mode 100644 index 0000000000..d82e58fbcd --- /dev/null +++ b/spec/data/root_alias_cookbooks/dup_attr/recipe.rb @@ -0,0 +1,3 @@ +ruby_block "root alias" do + block { } +end diff --git a/spec/data/root_alias_cookbooks/dup_recipe/attributes.rb b/spec/data/root_alias_cookbooks/dup_recipe/attributes.rb new file mode 100644 index 0000000000..3a3bab96e1 --- /dev/null +++ b/spec/data/root_alias_cookbooks/dup_recipe/attributes.rb @@ -0,0 +1 @@ +default["aliased"]["attr"] = "value" diff --git a/spec/data/root_alias_cookbooks/dup_recipe/metadata.rb b/spec/data/root_alias_cookbooks/dup_recipe/metadata.rb new file mode 100644 index 0000000000..62273a64d5 --- /dev/null +++ b/spec/data/root_alias_cookbooks/dup_recipe/metadata.rb @@ -0,0 +1,2 @@ +name "dup_recipe" +version "1.0.0" diff --git a/spec/data/root_alias_cookbooks/dup_recipe/recipe.rb b/spec/data/root_alias_cookbooks/dup_recipe/recipe.rb new file mode 100644 index 0000000000..d82e58fbcd --- /dev/null +++ b/spec/data/root_alias_cookbooks/dup_recipe/recipe.rb @@ -0,0 +1,3 @@ +ruby_block "root alias" do + block { } +end diff --git a/spec/data/root_alias_cookbooks/dup_recipe/recipes/default.rb b/spec/data/root_alias_cookbooks/dup_recipe/recipes/default.rb new file mode 100644 index 0000000000..3eb7c22809 --- /dev/null +++ b/spec/data/root_alias_cookbooks/dup_recipe/recipes/default.rb @@ -0,0 +1,3 @@ +ruby_block "other" do + block { } +end diff --git a/spec/data/root_alias_cookbooks/simple/attributes.rb b/spec/data/root_alias_cookbooks/simple/attributes.rb new file mode 100644 index 0000000000..3a3bab96e1 --- /dev/null +++ b/spec/data/root_alias_cookbooks/simple/attributes.rb @@ -0,0 +1 @@ +default["aliased"]["attr"] = "value" diff --git a/spec/data/root_alias_cookbooks/simple/metadata.rb b/spec/data/root_alias_cookbooks/simple/metadata.rb new file mode 100644 index 0000000000..9147558459 --- /dev/null +++ b/spec/data/root_alias_cookbooks/simple/metadata.rb @@ -0,0 +1,2 @@ +name "simple" +version "1.0.0" diff --git a/spec/data/root_alias_cookbooks/simple/recipe.rb b/spec/data/root_alias_cookbooks/simple/recipe.rb new file mode 100644 index 0000000000..d82e58fbcd --- /dev/null +++ b/spec/data/root_alias_cookbooks/simple/recipe.rb @@ -0,0 +1,3 @@ +ruby_block "root alias" do + block { } +end diff --git a/spec/functional/rebooter_spec.rb b/spec/functional/rebooter_spec.rb index 1b6e95b39c..a28491cc0b 100644 --- a/spec/functional/rebooter_spec.rb +++ b/spec/functional/rebooter_spec.rb @@ -72,6 +72,7 @@ describe Chef::Platform::Rebooter do def test_rebooter_method(method_sym, is_windows, expected_reboot_str) allow(ChefConfig).to receive(:windows?).and_return(is_windows) expect(rebooter).to receive(:shell_out!).once.with(expected_reboot_str) + expect(rebooter).to receive(:raise).with(Chef::Exceptions::Reboot) expect(rebooter).to receive(method_sym).once.and_call_original rebooter.send(method_sym, run_context.node) end diff --git a/spec/functional/resource/cron_spec.rb b/spec/functional/resource/cron_spec.rb index f5948191c5..1bff8bf874 100644 --- a/spec/functional/resource/cron_spec.rb +++ b/spec/functional/resource/cron_spec.rb @@ -1,7 +1,7 @@ # encoding: UTF-8 # # Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>) -# Copyright:: Copyright 2013-2016, Chef Software Inc. +# Copyright:: Copyright 2013-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -161,7 +161,7 @@ describe Chef::Resource::Cron, :requires_root, :unix_only do end def cron_create_should_raise_exception - expect { new_resource.run_action(:create) }.to raise_error(Chef::Exceptions::Cron, /Error updating state of #{new_resource.name}, exit: 1/) + expect { new_resource.run_action(:create) }.to raise_error(Chef::Exceptions::Cron) cron_should_not_exists(new_resource.name) end diff --git a/spec/functional/resource/template_spec.rb b/spec/functional/resource/template_spec.rb index 32529fbb0c..b9a39255f4 100644 --- a/spec/functional/resource/template_spec.rb +++ b/spec/functional/resource/template_spec.rb @@ -32,6 +32,7 @@ describe Chef::Resource::Template do let(:node) do node = Chef::Node.new node.normal[:slappiness] = "a warm gun" + node.normal[:nested][:secret] = "value" node end @@ -209,4 +210,36 @@ describe Chef::Resource::Template do end end + describe "when template variables contain lazy{} calls" do + it "resolves the DelayedEvaluator" do + resource.source("openldap_variable_stuff.conf.erb") + resource.variables(:secret => Chef::DelayedEvaluator.new { "nutella" }) + resource.run_action(:create) + expect(IO.read(path)).to eq("super secret is nutella") + end + + it "does not mutate the resource variables" do + resource.source("openldap_variable_stuff.conf.erb") + resource.variables(:secret => Chef::DelayedEvaluator.new { "nutella" }) + resource.run_action(:create) + expect(resource.variables[:secret]).to be_a Chef::DelayedEvaluator + end + + it "resolves the DelayedEvaluator when deeply nested" do + resource.source("openldap_nested_variable_stuff.erb") + resource.variables(:secret => [{ "key" => Chef::DelayedEvaluator.new { "nutella" } }]) + resource.run_action(:create) + expect(IO.read(path)).to eq("super secret is nutella") + end + end + + describe "when passing a node attribute mash as a template variable" do + it "uses the node attributes like a hash" do + resource.source("openldap_variable_stuff.conf.erb") + resource.variables(node[:nested]) + resource.run_action(:create) + expect(IO.read(path)).to eq("super secret is value") + end + end + end diff --git a/spec/functional/root_alias_spec.rb b/spec/functional/root_alias_spec.rb new file mode 100644 index 0000000000..7091615d12 --- /dev/null +++ b/spec/functional/root_alias_spec.rb @@ -0,0 +1,78 @@ +# +# Copyright:: Copyright 2017, Noah Kantrowitz +# 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 "root aliases" do + let(:chef_repo_path) { File.expand_path(File.join(CHEF_SPEC_DATA, "root_alias_cookbooks")) } + let(:cookbook_collection) do + cl = Chef::CookbookLoader.new(chef_repo_path) + cl.load_cookbooks + Chef::CookbookCollection.new(cl) + end + let(:node) do + node = Chef::Node.new + node.automatic[:recipes] = [] + node + end + let(:events) { Chef::EventDispatch::Dispatcher.new } + let(:run_context) { Chef::RunContext.new(node, cookbook_collection, events) } + before do + node.run_context = run_context + end + + describe "attributes root aliases" do + it "should load attributes.rb when included directly" do + node.include_attribute("simple") + expect(node["aliased"]["attr"]).to eq "value" + end + + it "should load attributes.rb when loading a cookbook" do + node.run_list << "simple" + run_context.load(node.run_list.expand("_default")) + expect(node["aliased"]["attr"]).to eq "value" + end + + context "with both an attributes.rb and attributes/default.rb" do + it "should log an error and ignore attributes/default.rb" do + expect(Chef::Log).to receive(:error).with("Cookbook dup_attr contains both attributes.rb and and attributes/default.rb, ignoring attributes/default.rb") + node.run_list << "dup_attr" + run_context.load(node.run_list.expand("_default")) + expect(node["aliased"]["attr"]).to eq "value" + end + end + end + + describe "recipe root aliased" do + it "should load recipe.rb" do + node.run_list << "simple" + run_context.load(node.run_list.expand("_default")) + run_context.include_recipe("simple") + expect(run_context.resource_collection.map(&:to_s)).to eq ["ruby_block[root alias]"] + end + + context "with both an recipe.rb and recipes/default.rb" do + it "should log an error and ignore recipes/default.rb" do + expect(Chef::Log).to receive(:error).with("Cookbook dup_recipe contains both recipe.rb and and recipes/default.rb, ignoring recipes/default.rb") + node.run_list << "dup_recipe" + run_context.load(node.run_list.expand("_default")) + run_context.include_recipe("dup_recipe") + expect(run_context.resource_collection.map(&:to_s)).to eq ["ruby_block[root alias]"] + end + end + end +end diff --git a/spec/functional/shell_spec.rb b/spec/functional/shell_spec.rb index 8c8d7ba482..08c791f2d2 100644 --- a/spec/functional/shell_spec.rb +++ b/spec/functional/shell_spec.rb @@ -1,6 +1,6 @@ # # Author:: Daniel DeLeo (<dan@chef.io>) -# Copyright:: Copyright 2012-2016, Chef Software, Inc. +# Copyright:: Copyright 2012-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,6 @@ require "spec_helper" require "functional/resource/base" require "chef/version" require "chef/shell" -require "chef/mixin/command/unix" describe Shell do @@ -28,7 +27,6 @@ describe Shell do # not catch cases where chef-shell fails to boot because of changes in # chef/client.rb describe "smoke tests", :unix_only => true do - include Chef::Mixin::Command::Unix TIMEOUT = 300 @@ -79,44 +77,26 @@ describe Shell do end def run_chef_shell_with(options) - case ohai[:platform] - when "aix" - config = File.expand_path("shef-config.rb", CHEF_SPEC_DATA) - path_to_chef_shell = File.expand_path("../../../bin/chef-shell", __FILE__) - output = "" - status = popen4("#{path_to_chef_shell} -c #{config} #{options}", :waitlast => true) do |pid, stdin, stdout, stderr| - read_until(stdout, "chef (#{Chef::VERSION})>") - yield stdout, stdin if block_given? - stdin.write("'done'\n") - output = read_until(stdout, '=> "done"') - stdin.print("exit\n") - flush_output(stdout) - end - - [output, status.exitstatus] - else - # Windows ruby installs don't (always?) have PTY, - # so hide the require here - begin - require "pty" - config = File.expand_path("shef-config.rb", CHEF_SPEC_DATA) - path_to_chef_shell = File.expand_path("../../../bin/chef-shell", __FILE__) - reader, writer, pid = PTY.spawn("#{path_to_chef_shell} -c #{config} #{options}") - read_until(reader, "chef (#{Chef::VERSION})>") - yield reader, writer if block_given? - writer.puts('"done"') - output = read_until(reader, '=> "done"') - writer.print("exit\n") - flush_output(reader) - writer.close - - exitstatus = wait_or_die(pid) - - [output, exitstatus] - rescue PTY::ChildExited => e - [output, e.status] - end - end + # Windows ruby installs don't (always?) have PTY, + # so hide the require here + + require "pty" + config = File.expand_path("shef-config.rb", CHEF_SPEC_DATA) + path_to_chef_shell = File.expand_path("../../../bin/chef-shell", __FILE__) + reader, writer, pid = PTY.spawn("#{path_to_chef_shell} -c #{config} #{options}") + read_until(reader, "chef (#{Chef::VERSION})>") + yield reader, writer if block_given? + writer.puts('"done"') + output = read_until(reader, '=> "done"') + writer.print("exit\n") + flush_output(reader) + writer.close + + exitstatus = wait_or_die(pid) + + [output, exitstatus] + rescue PTY::ChildExited => e + [output, e.status] end it "boots correctly with -lauto" do diff --git a/spec/integration/client/client_spec.rb b/spec/integration/client/client_spec.rb index 2a31638c0f..00086c75ca 100644 --- a/spec/integration/client/client_spec.rb +++ b/spec/integration/client/client_spec.rb @@ -294,10 +294,9 @@ 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 = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" -r 'x::default' -z -l info", :cwd => chef_dir) expect(result.stdout).not_to include("Overridden Run List") expect(result.stdout).to include("Run List is [recipe[x::default]]") - #puts result.stdout result.error! end @@ -445,7 +444,7 @@ control_group "control group without top level control" do end RECIPE - result = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'audit_test::succeed'", :cwd => chef_dir) + result = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'audit_test::succeed' -l info", :cwd => chef_dir) expect(result.error?).to be_falsey expect(result.stdout).to include("Successfully executed all `control_group` blocks and contained examples") end @@ -465,6 +464,39 @@ end end end + when_the_repository "has a cookbook with an ohai plugin" do + before do + file "cookbooks/x/recipes/default.rb", <<-RECIPE +file #{path_to('tempfile.txt').inspect} do + content node["english"]["version"] +end + RECIPE + + file "cookbooks/x/ohai/english.rb", <<-OHAI + Ohai.plugin(:English) do + provides 'english' + + collect_data do + english Mash.new + english[:version] = "2014" + end + end + OHAI + + file "config/client.rb", <<EOM +local_mode true +cookbook_path "#{path_to('cookbooks')}" +EOM + end + + it "should run the ohai plugin" do + result = shell_out("#{chef_client} -l debug -c \"#{path_to('config/client.rb')}\" -o 'x::default' --no-fork", :cwd => chef_dir) + result.error! + + expect(IO.read(path_to("tempfile.txt"))).to eq("2014") + end + end + # Fails on appveyor, but works locally on windows and on windows hosts in Ci. context "when using recipe-url", :skip_appveyor do before(:each) do @@ -522,4 +554,42 @@ EOM command.error! end end + + when_the_repository "has a cookbook that logs at the info level" do + before do + file "cookbooks/x/recipes/default.rb", <<EOM + log "info level" do + level :info + end +EOM + file "config/client.rb", <<EOM +local_mode true +cookbook_path "#{path_to('cookbooks')}" +EOM + end + + it "a chef client run should not log to info by default" do + command = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default' --no-fork", :cwd => chef_dir) + command.error! + expect(command.stdout).not_to include("INFO") + end + + it "a chef client run to a pipe should not log to info by default" do + command = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default' --no-fork | tee #{path_to('chefrun.out')}", :cwd => chef_dir) + command.error! + expect(command.stdout).not_to include("INFO") + end + + it "a chef solo run should not log to info by default" do + command = shell_out("#{chef_solo} -c \"#{path_to('config/client.rb')}\" -o 'x::default' --no-fork", :cwd => chef_dir) + command.error! + expect(command.stdout).not_to include("INFO") + end + + it "a chef solo run to a pipe should not log to info by default" do + command = shell_out("#{chef_solo} -c \"#{path_to('config/client.rb')}\" -o 'x::default' --no-fork | tee #{path_to('chefrun.out')}", :cwd => chef_dir) + command.error! + expect(command.stdout).not_to include("INFO") + end + end end diff --git a/spec/integration/client/exit_code_spec.rb b/spec/integration/client/exit_code_spec.rb index 30020f6a3f..4397426723 100644 --- a/spec/integration/client/exit_code_spec.rb +++ b/spec/integration/client/exit_code_spec.rb @@ -25,7 +25,7 @@ describe "chef-client" do let(:critical_env_vars) { %w{PATH RUBYOPT BUNDLE_GEMFILE GEM_PATH}.map { |o| "#{o}=#{ENV[o]}" } .join(" ") } - when_the_repository "does not have exit_status configured" do + when_the_repository "uses RFC 062 defined exit codes" do def setup_client_rb file "config/client.rb", <<EOM @@ -43,110 +43,6 @@ EOM end def run_chef_client_and_expect_exit_code(exit_code) - shell_out!( - "#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default'", - :cwd => chef_dir, - :returns => [exit_code]) - end - - context "has a cookbook" do - context "with a library" do - context "which cannot be loaded" do - before do - file "cookbooks/x/recipes/default.rb", "" - file "cookbooks/x/libraries/error.rb", "require 'does/not/exist'" - end - - it "exits with GENERIC_FAILURE, 1" do - setup_client_rb - run_chef_client_and_expect_exit_code 1 - end - end - end - - context "with an audit recipe" do - context "which fails" do - before do - file "cookbooks/x/recipes/default.rb", <<-RECIPE -control_group "control group without top level control" do - it "should fail" do - expect(2 - 2).to eq(1) - end -end -RECIPE - end - - it "exits with GENERIC_FAILURE, 1" do - setup_client_rb_with_audit_mode - run_chef_client_and_expect_exit_code 1 - end - end - end - - context "with a recipe" do - context "which throws an error" do - before { file "cookbooks/x/recipes/default.rb", "raise 'BOOM'" } - - it "exits with GENERIC_FAILURE, 1" do - setup_client_rb - run_chef_client_and_expect_exit_code 1 - end - end - - context "with a recipe which calls Chef::Application.fatal with a non-RFC exit code" do - before { file "cookbooks/x/recipes/default.rb", "Chef::Application.fatal!('BOOM', 123)" } - - it "exits with the specified exit code" do - setup_client_rb - run_chef_client_and_expect_exit_code 123 - end - end - - context "with a recipe which calls Chef::Application.exit with a non-RFC exit code" do - before { file "cookbooks/x/recipes/default.rb", "Chef::Application.exit!('BOOM', 231)" } - - it "exits with the specified exit code" do - setup_client_rb - run_chef_client_and_expect_exit_code 231 - end - end - end - - context "when an attempt to reboot fails (like from the reboot resource)" do - before do - file "cookbooks/x/recipes/default.rb", <<EOM -raise Chef::Exceptions::RebootFailed.new -EOM - end - - it "exits with GENERIC_FAILURE, 1" do - setup_client_rb - run_chef_client_and_expect_exit_code 1 - end - end - end - end - - when_the_repository "does has exit_status enabled" do - - def setup_client_rb - file "config/client.rb", <<EOM -local_mode true -cookbook_path "#{path_to('cookbooks')}" -exit_status :enabled -EOM - end - - def setup_client_rb_with_audit_mode - file "config/client.rb", <<EOM -local_mode true -cookbook_path "#{path_to('cookbooks')}" -exit_status :enabled -audit_mode :audit_only -EOM - end - - def run_chef_client_and_expect_exit_code(exit_code) shell_out!("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default'", :cwd => chef_dir, :returns => [exit_code]) diff --git a/spec/integration/knife/chef_repository_file_system_spec.rb b/spec/integration/knife/chef_repository_file_system_spec.rb index cc538c98c0..222d3aee8a 100644 --- a/spec/integration/knife/chef_repository_file_system_spec.rb +++ b/spec/integration/knife/chef_repository_file_system_spec.rb @@ -158,103 +158,6 @@ EOM end end - when_the_repository "has extraneous subdirectories and files under a cookbook" do - before do - directory "cookbooks/cookbook1" do - file "a.rb", "" - file "blarghle/blah.rb", "" - directory "attributes" do - file "a.rb", "" - file "b.json", {} - file "c/d.rb", "" - file "c/e.json", {} - end - directory "definitions" do - file "a.rb", "" - file "b.json", {} - file "c/d.rb", "" - file "c/e.json", {} - end - directory "recipes" do - file "a.rb", "" - file "b.json", {} - file "c/d.rb", "" - file "c/e.json", {} - end - directory "libraries" do - file "a.rb", "" - file "b.json", {} - file "c/d.rb", "" - file "c/e.json", {} - end - directory "templates" do - file "a.rb", "" - file "b.json", {} - file "c/d.rb", "" - file "c/e.json", {} - end - directory "files" do - file "a.rb", "" - file "b.json", {} - file "c/d.rb", "" - file "c/e.json", {} - end - directory "resources" do - file "a.rb", "" - file "b.json", {} - file "c/d.rb", "" - file "c/e.json", {} - end - directory "providers" do - file "a.rb", "" - file "b.json", {} - file "c/d.rb", "" - file "c/e.json", {} - end - end - end - - it "knife list --local -Rfp / should NOT return them" do - knife("list --local -Rfp /").should_succeed <<EOM -/cookbooks/ -/cookbooks/cookbook1/ -/cookbooks/cookbook1/a.rb -/cookbooks/cookbook1/attributes/ -/cookbooks/cookbook1/attributes/a.rb -/cookbooks/cookbook1/definitions/ -/cookbooks/cookbook1/definitions/a.rb -/cookbooks/cookbook1/files/ -/cookbooks/cookbook1/files/a.rb -/cookbooks/cookbook1/files/b.json -/cookbooks/cookbook1/files/c/ -/cookbooks/cookbook1/files/c/d.rb -/cookbooks/cookbook1/files/c/e.json -/cookbooks/cookbook1/libraries/ -/cookbooks/cookbook1/libraries/a.rb -/cookbooks/cookbook1/libraries/b.json -/cookbooks/cookbook1/libraries/c/ -/cookbooks/cookbook1/libraries/c/d.rb -/cookbooks/cookbook1/libraries/c/e.json -/cookbooks/cookbook1/providers/ -/cookbooks/cookbook1/providers/a.rb -/cookbooks/cookbook1/providers/c/ -/cookbooks/cookbook1/providers/c/d.rb -/cookbooks/cookbook1/recipes/ -/cookbooks/cookbook1/recipes/a.rb -/cookbooks/cookbook1/resources/ -/cookbooks/cookbook1/resources/a.rb -/cookbooks/cookbook1/resources/c/ -/cookbooks/cookbook1/resources/c/d.rb -/cookbooks/cookbook1/templates/ -/cookbooks/cookbook1/templates/a.rb -/cookbooks/cookbook1/templates/b.json -/cookbooks/cookbook1/templates/c/ -/cookbooks/cookbook1/templates/c/d.rb -/cookbooks/cookbook1/templates/c/e.json -EOM - end - end - when_the_repository "has a file in cookbooks/" do before { file "cookbooks/file", "" } it "does not show up in list -Rfp" do diff --git a/spec/integration/knife/cookbook_download_spec.rb b/spec/integration/knife/cookbook_download_spec.rb index 2fbffb9dea..2e64cac133 100644 --- a/spec/integration/knife/cookbook_download_spec.rb +++ b/spec/integration/knife/cookbook_download_spec.rb @@ -35,14 +35,6 @@ describe "knife cookbook download", :workstation do it "knife cookbook download downloads the latest version" do knife("cookbook download -d #{tmpdir} x").should_succeed stderr: <<EOM Downloading x cookbook version 1.0.1 -Downloading resources -Downloading providers -Downloading recipes -Downloading definitions -Downloading libraries -Downloading attributes -Downloading files -Downloading templates Downloading root_files Cookbook downloaded to #{tmpdir}/x-1.0.1 EOM @@ -51,14 +43,6 @@ EOM it "knife cookbook download with a version downloads the specified version" do knife("cookbook download -d #{tmpdir} x 1.0.1").should_succeed stderr: <<EOM Downloading x cookbook version 1.0.1 -Downloading resources -Downloading providers -Downloading recipes -Downloading definitions -Downloading libraries -Downloading attributes -Downloading files -Downloading templates Downloading root_files Cookbook downloaded to #{tmpdir}/x-1.0.1 EOM @@ -78,14 +62,6 @@ EOM it "knife cookbook download with no version prompts" do knife("cookbook download -d #{tmpdir} x", input: "2\n").should_succeed(stderr: <<EOM, stdout: "Which version do you want to download?\n1. x 1.0.0\n2. x 1.0.1\n\n" Downloading x cookbook version 1.0.1 -Downloading resources -Downloading providers -Downloading recipes -Downloading definitions -Downloading libraries -Downloading attributes -Downloading files -Downloading templates Downloading root_files Cookbook downloaded to #{tmpdir}/x-1.0.1 EOM diff --git a/spec/integration/knife/cookbook_show_spec.rb b/spec/integration/knife/cookbook_show_spec.rb index c001d66b97..1ccf7ffcb7 100644 --- a/spec/integration/knife/cookbook_show_spec.rb +++ b/spec/integration/knife/cookbook_show_spec.rb @@ -37,22 +37,14 @@ describe "knife cookbook show", :workstation do # rubocop:disable Style/TrailingWhitespace it "knife cookbook show x 1.0.0 shows the correct version" do knife("cookbook show x 1.0.0").should_succeed <<EOM -attributes: -chef_type: cookbook_version cookbook_name: x -definitions: -files: frozen?: false -json_class: Chef::CookbookVersion -libraries: metadata: attributes: chef_versions: - conflicting: dependencies: description: gems: - groupings: issues_url: license: All rights reserved long_description: @@ -63,34 +55,32 @@ metadata: platforms: privacy: false providing: + x: >= 0.0.0 + x::x: >= 0.0.0 recipes: - recommendations: - replacing: + x: + x::x: source_url: - suggestions: version: 1.0.0 name: x-1.0.0 -providers: recipes: checksum: 4631b34cf58de10c5ef1304889941b2e - name: default.rb + name: recipes/default.rb path: recipes/default.rb specificity: default url: http://127.0.0.1:8900/file_store/checksums/4631b34cf58de10c5ef1304889941b2e checksum: d41d8cd98f00b204e9800998ecf8427e - name: x.rb + name: recipes/x.rb path: recipes/x.rb specificity: default url: http://127.0.0.1:8900/file_store/checksums/d41d8cd98f00b204e9800998ecf8427e -resources: root_files: checksum: 8226671f751ba102dea6a6b6bd32fa8d name: metadata.rb path: metadata.rb specificity: default url: http://127.0.0.1:8900/file_store/checksums/8226671f751ba102dea6a6b6bd32fa8d -templates: version: 1.0.0 EOM end @@ -99,11 +89,9 @@ EOM knife("cookbook show x 1.0.0 metadata").should_succeed <<EOM attributes: chef_versions: -conflicting: dependencies: description: gems: -groupings: issues_url: license: All rights reserved long_description: @@ -114,11 +102,12 @@ ohai_versions: platforms: privacy: false providing: + x: >= 0.0.0 + x::x: >= 0.0.0 recipes: -recommendations: -replacing: + x: + x::x: source_url: -suggestions: version: 1.0.0 EOM end @@ -126,13 +115,13 @@ EOM it "knife cookbook show x 1.0.0 recipes shows all the recipes" do knife("cookbook show x 1.0.0 recipes").should_succeed <<EOM checksum: 4631b34cf58de10c5ef1304889941b2e -name: default.rb +name: recipes/default.rb path: recipes/default.rb specificity: default url: http://127.0.0.1:8900/file_store/checksums/4631b34cf58de10c5ef1304889941b2e checksum: d41d8cd98f00b204e9800998ecf8427e -name: x.rb +name: recipes/x.rb path: recipes/x.rb specificity: default url: http://127.0.0.1:8900/file_store/checksums/d41d8cd98f00b204e9800998ecf8427e diff --git a/spec/integration/knife/search_node_spec.rb b/spec/integration/knife/search_node_spec.rb new file mode 100644 index 0000000000..e3cda1a138 --- /dev/null +++ b/spec/integration/knife/search_node_spec.rb @@ -0,0 +1,39 @@ +# +# Copyright:: Copyright 2013-2017, Chef Software Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require "support/shared/integration/integration_helper" +require "support/shared/context/config" + +describe "knife node show", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + + when_the_chef_server "has a node with a run_list" do + before do + node "cons", { run_list: ["recipe[bar]", "recipe[foo]"] } + end + + it "finds the node" do + knife("search node name:cons").should_succeed(/Node Name:\s*cons/, stderr: "1 items found\n\n") + end + + it "does not find a node" do + knife("search node name:snoc").should_fail("", stderr: "0 items found\n\n", exit_code: 1) + end + end +end diff --git a/spec/integration/knife/serve_spec.rb b/spec/integration/knife/serve_spec.rb index 72f0bb59ed..b0cdd8c070 100644 --- a/spec/integration/knife/serve_spec.rb +++ b/spec/integration/knife/serve_spec.rb @@ -24,33 +24,69 @@ describe "knife serve", :workstation do include KnifeSupport include AppServerSupport + def with_knife_serve + exception = nil + t = Thread.new do + begin + knife("serve --chef-zero-port=8890") + rescue + exception = $! + end + end + begin + Chef::Config.log_level = :debug + Chef::Config.chef_server_url = "http://localhost:8890" + Chef::Config.node_name = nil + Chef::Config.client_key = nil + api = Chef::ServerAPI.new + yield api + rescue + if exception + raise exception + else + raise + end + ensure + t.kill + sleep 0.5 + end + end + when_the_repository "also has one of each thing" do - before { file "nodes/x.json", { "foo" => "bar" } } + before do + file "nodes/a_node_in_json.json", { "foo" => "bar" } + file "nodes/a_node_in_ruby.rb", "name 'a_node_in_ruby'" + file "roles/a_role_in_json.json", { "foo" => "bar" } + file "roles/a_role_in_ruby.rb", "name 'a_role_in_ruby'" + end - it "knife serve serves up /nodes/x" do - exception = nil - t = Thread.new do - begin - knife("serve --chef-zero-port=8890") - rescue - exception = $! + %w{a_node_in_json a_node_in_ruby}.each do |file_type| + context file_type do + it "knife serve serves up /nodes" do + with_knife_serve do |api| + expect(api.get("nodes")).to have_key(file_type) + end + end + it "knife serve serves up /nodes/#{file_type}" do + with_knife_serve do |api| + expect(api.get("nodes/#{file_type}")["name"]).to eq(file_type) + end end end - begin - Chef::Config.log_level = :debug - Chef::Config.chef_server_url = "http://localhost:8890" - Chef::Config.node_name = nil - Chef::Config.client_key = nil - api = Chef::ServerAPI.new - expect(api.get("nodes/x")["name"]).to eq("x") - rescue - if exception - raise exception - else - raise + end + + %w{a_role_in_json a_role_in_ruby}.each do |file_type| + context file_type do + it "knife serve serves up /roles" do + with_knife_serve do |api| + expect(api.get("roles")).to have_key(file_type) + end + end + it "knife serve serves up /roles/#{file_type}" do + with_knife_serve do |api| + expect(api.get("roles/#{file_type}")["name"]).to eq(file_type) + end end - ensure - t.kill end end end diff --git a/spec/integration/recipes/lwrp_inline_resources_spec.rb b/spec/integration/recipes/lwrp_inline_resources_spec.rb index 65931d4764..54ce94f263 100644 --- a/spec/integration/recipes/lwrp_inline_resources_spec.rb +++ b/spec/integration/recipes/lwrp_inline_resources_spec.rb @@ -19,14 +19,13 @@ describe "LWRPs with inline resources" do let(:chef_client) { "ruby '#{chef_dir}/chef-client' --minimal-ohai" } context "with a use_inline_resources provider with 'def action_a' instead of action :a" do - class LwrpInlineResourcesTest < Chef::Resource::LWRPBase + class LwrpInlineResourcesTest < Chef::Resource resource_name :lwrp_inline_resources_test - actions :a, :nothing + allowed_actions :a, :nothing default_action :a property :ran_a class Provider < Chef::Provider::LWRPBase provides :lwrp_inline_resources_test - use_inline_resources def action_a r = new_resource ruby_block "run a" do @@ -46,10 +45,10 @@ describe "LWRPs with inline resources" do end context "with an inline resource with a property that shadows the enclosing provider's property" do - class LwrpShadowedPropertyTest < Chef::Resource::LWRPBase + class LwrpShadowedPropertyTest < Chef::Resource PATH = ::File.join(Dir.tmpdir, "shadow-property.txt") use_automatic_resource_name - actions :fiddle + allowed_actions :fiddle property :content action :fiddle do file PATH do @@ -73,16 +72,14 @@ describe "LWRPs with inline resources" do end context "with an inline_resources provider with two actions, one calling the other" do - class LwrpInlineResourcesTest2 < Chef::Resource::LWRPBase + class LwrpInlineResourcesTest2 < Chef::Resource resource_name :lwrp_inline_resources_test2 - actions :a, :b, :nothing + allowed_actions :a, :b, :nothing default_action :b property :ran_a property :ran_b class Provider < Chef::Provider::LWRPBase provides :lwrp_inline_resources_test2 - use_inline_resources - action :a do r = new_resource ruby_block "run a" do @@ -133,8 +130,7 @@ describe "LWRPs with inline resources" do default_action :create EOM file "providers/my_machine.rb", <<-EOM - use_inline_resources - action :create do + action :create do x_do_nothing 'a' x_do_nothing 'b' end diff --git a/spec/integration/recipes/notifies_spec.rb b/spec/integration/recipes/notifies_spec.rb index 6f6a0f06b0..b008e4ade7 100644 --- a/spec/integration/recipes/notifies_spec.rb +++ b/spec/integration/recipes/notifies_spec.rb @@ -362,4 +362,33 @@ EOM result.error! end end + + when_the_repository "has resources that have arrays as the name" do + before do + directory "cookbooks/x" do + file "recipes/default.rb", <<-EOM + log [ "a", "b" ] do + action :nothing + end + + log "doit" do + notifies :write, "log[a, b]" + end + EOM + end + end + + it "notifying the resource should work" do + file "config/client.rb", <<EOM +local_mode true +cookbook_path "#{path_to('cookbooks')}" +log_level :warn +EOM + + result = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" --no-color -F doc -o 'x::default'", :cwd => chef_dir) + expect(result.stdout).to match /\* log\[a, b\] action write/ + result.error! + end + + end end diff --git a/spec/support/shared/context/client.rb b/spec/support/shared/context/client.rb index c65650e6b1..19ce82fa15 100644 --- a/spec/support/shared/context/client.rb +++ b/spec/support/shared/context/client.rb @@ -129,12 +129,18 @@ shared_context "a client run" do # ---Client#sync_cookbooks -- downloads the list of cookbooks to sync # expect_any_instance_of(Chef::CookbookSynchronizer).to receive(:sync_cookbooks) - expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_cookbook_sync) + expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_url], version_class: Chef::CookbookManifestVersions).and_return(http_cookbook_sync) expect(http_cookbook_sync).to receive(:post). with("environments/_default/cookbook_versions", { :run_list => [] }). and_return({}) end + def stub_for_required_recipe + response = Net::HTTPNotFound.new("1.1", "404", "Not Found") + exception = Net::HTTPServerException.new('404 "Not Found"', response) + expect(http_node_load).to receive(:get).with("required_recipe").and_raise(exception) + end + def stub_for_converge # define me end @@ -165,6 +171,7 @@ shared_context "a client run" do stub_for_data_collector_init stub_for_node_load stub_for_sync_cookbooks + stub_for_required_recipe stub_for_converge stub_for_audit stub_for_node_save diff --git a/spec/support/shared/integration/knife_support.rb b/spec/support/shared/integration/knife_support.rb index 4efa30a003..d873723784 100644 --- a/spec/support/shared/integration/knife_support.rb +++ b/spec/support/shared/integration/knife_support.rb @@ -1,6 +1,6 @@ # # Author:: John Keiser (<jkeiser@chef.io>) -# Copyright:: Copyright 2013-2016, Chef Software Inc. +# Copyright:: Copyright 2013-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/spec/unit/application/exit_code_spec.rb b/spec/unit/application/exit_code_spec.rb index 5abf19fc02..7783cf3ed7 100644 --- a/spec/unit/application/exit_code_spec.rb +++ b/spec/unit/application/exit_code_spec.rb @@ -70,92 +70,11 @@ describe Chef::Application::ExitCode do end end - context "when Chef::Config :exit_status is not configured" do - before do - allow(Chef::Config).to receive(:[]).with(:exit_status).and_return(nil) - allow(Chef::Config).to receive(:[]).with(:treat_deprecation_warnings_as_errors).and_return(false) - end - - it "writes a deprecation warning" do - expect(Chef).to receive(:deprecated).with(:exit_code, /^Chef RFC 062/) - expect(exit_codes.normalize_exit_code(151)).to eq(151) - end - - it "does not modify non-RFC exit codes" do - expect(exit_codes.normalize_exit_code(151)).to eq(151) - end - - it "returns DEPRECATED_FAILURE when no exit code is specified" do - expect(exit_codes.normalize_exit_code()).to eq(-1) - end - - it "returns SIGINT_RECEIVED when a SIGINT is received" do - expect(exit_codes.normalize_exit_code(Chef::Exceptions::SigInt.new("BOOM"))).to eq(2) - end - - it "returns SIGTERM_RECEIVED when a SIGTERM is received" do - expect(exit_codes.normalize_exit_code(Chef::Exceptions::SigTerm.new("BOOM"))).to eq(3) - end - - it "returns SIGINT_RECEIVED when a deprecated exit code error is received" do - expect(exit_codes.normalize_exit_code(Chef::Exceptions::DeprecatedExitCode.new("BOOM"))).to eq(2) - end - - it "returns GENERIC_FAILURE when an exception is specified" do - expect(exit_codes.normalize_exit_code(Exception.new("BOOM"))).to eq(1) - end - - end - - context "when Chef::Config :exit_status is configured to not validate exit codes" do - before do - allow(Chef::Config).to receive(:[]).with(:exit_status).and_return(:disabled) - allow(Chef::Config).to receive(:[]).with(:treat_deprecation_warnings_as_errors).and_return(false) - end - - it "does not write a deprecation warning" do - expect(Chef).not_to receive(:deprecated).with(:exit_code, /^Chef RFC 062/) - expect(exit_codes.normalize_exit_code(151)).to eq(151) - end - - it "does not modify non-RFC exit codes" do - expect(exit_codes.normalize_exit_code(151)).to eq(151) - end - - it "returns DEPRECATED_FAILURE when no exit code is specified" do - expect(exit_codes.normalize_exit_code()).to eq(-1) - end - - it "returns GENERIC_FAILURE when an exception is specified" do - expect(exit_codes.normalize_exit_code(Exception.new("BOOM"))).to eq(1) - end - - it "returns SUCCESS when a reboot is pending" do - allow(Chef::DSL::RebootPending).to receive(:reboot_pending?).and_return(true) - expect(exit_codes.normalize_exit_code(0)).to eq(0) - end - - it "returns SIGINT_RECEIVED when a SIGINT is received" do - expect(exit_codes.normalize_exit_code(Chef::Exceptions::SigInt.new("BOOM"))).to eq(2) - end - - it "returns SIGTERM_RECEIVED when a SIGTERM is received" do - expect(exit_codes.normalize_exit_code(Chef::Exceptions::SigTerm.new("BOOM"))).to eq(3) - end - - it "returns SIGINT_RECEIVED when a deprecated exit code error is received" do - expect(exit_codes.normalize_exit_code(Chef::Exceptions::DeprecatedExitCode.new("BOOM"))).to eq(2) - end - end + context "when Chef validates exit codes" do - context "when Chef::Config :exit_status is configured to validate exit codes" do - before do - allow(Chef::Config).to receive(:[]).with(:exit_status).and_return(:enabled) - allow(Chef::Config).to receive(:[]).with(:treat_deprecation_warnings_as_errors).and_return(false) - end - - it "does write a deprecation warning" do - expect(Chef).to receive(:deprecated).with(:exit_code, /^Chef RFC 062/) + it "does write a warning on non-standard exit codes" do + expect(Chef::Log).to receive(:warn).with( + /^Chef attempted to exit with a non-standard exit code of 151/) expect(exit_codes.normalize_exit_code(151)).to eq(1) end @@ -175,10 +94,6 @@ describe Chef::Application::ExitCode do expect(exit_codes.normalize_exit_code(Chef::Exceptions::SigTerm.new("BOOM"))).to eq(3) end - it "returns GENERIC_FAILURE when a deprecated exit code error is received" do - expect(exit_codes.normalize_exit_code(Chef::Exceptions::DeprecatedExitCode.new("BOOM"))).to eq(1) - end - it "returns GENERIC_FAILURE when an exception is specified" do expect(exit_codes.normalize_exit_code(Exception.new("BOOM"))).to eq(1) end diff --git a/spec/unit/application_spec.rb b/spec/unit/application_spec.rb index 867cd3f9c2..7981748962 100644 --- a/spec/unit/application_spec.rb +++ b/spec/unit/application_spec.rb @@ -1,7 +1,7 @@ # # Author:: AJ Christensen (<aj@junglist.gen.nz>) # Author:: Mark Mzyk (mmzyk@chef.io) -# Copyright:: Copyright 2008-2016, Chef Software Inc. +# Copyright:: Copyright 2008-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -187,53 +187,56 @@ describe Chef::Application do allow(Chef::Log).to receive(:level=) @monologger = double("Monologger") expect(MonoLogger).to receive(:new).with(Chef::Config[:log_location]).and_return(@monologger) + allow(MonoLogger).to receive(:new).with(STDOUT).and_return(@monologger) + allow(@monologger).to receive(:formatter=).with(Chef::Log.logger.formatter) expect(Chef::Log).to receive(:init).with(@monologger) @app.configure_logging end shared_examples_for "log_level_is_auto" do - context "when STDOUT is to a tty" do + before do + allow(STDOUT).to receive(:tty?).and_return(true) + end + + it "configures the log level to :warn" do + @app.configure_logging + expect(Chef::Log.level).to eq(:warn) + end + + context "when force_formater is configured" do before do - allow(STDOUT).to receive(:tty?).and_return(true) + Chef::Config[:force_formatter] = true end - it "configures the log level to :warn" do + it "configures the log level to warn" do @app.configure_logging expect(Chef::Log.level).to eq(:warn) end - - context "when force_logger is configured" do - before do - Chef::Config[:force_logger] = true - end - - it "configures the log level to info" do - @app.configure_logging - expect(Chef::Log.level).to eq(:info) - end - end end - context "when STDOUT is not to a tty" do + context "when force_logger is configured" do before do - allow(STDOUT).to receive(:tty?).and_return(false) + Chef::Config[:force_logger] = true end - it "configures the log level to :info" do + it "configures the log level to info" do @app.configure_logging expect(Chef::Log.level).to eq(:info) end + end - context "when force_formatter is configured" do - before do - Chef::Config[:force_formatter] = true - end - it "sets the log level to :warn" do - @app.configure_logging - expect(Chef::Log.level).to eq(:warn) - end + context "when both are is configured" do + before do + Chef::Config[:force_logger] = true + Chef::Config[:force_formatter] = true + end + + it "configures the log level to warn" do + @app.configure_logging + expect(Chef::Log.level).to eq(:warn) end end + end context "when log_level is not set" do @@ -299,16 +302,23 @@ describe Chef::Application do Chef::Application.fatal! "blah" end - describe "when an exit code is supplied" do + describe "when a standard exit code is supplied" do it "should exit with the given exit code" do - expect(Process).to receive(:exit).with(-100).and_return(true) + expect(Process).to receive(:exit).with(42).and_return(true) + Chef::Application.fatal! "blah", 42 + end + end + + describe "when a non-standard exit code is supplied" do + it "should exit with the default exit code" do + expect(Process).to receive(:exit).with(1).and_return(true) Chef::Application.fatal! "blah", -100 end end describe "when an exit code is not supplied" do it "should exit with the default exit code" do - expect(Process).to receive(:exit).with(-1).and_return(true) + expect(Process).to receive(:exit).with(1).and_return(true) Chef::Application.fatal! "blah" end end diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb index ec3f70b9b0..d348c24385 100644 --- a/spec/unit/client_spec.rb +++ b/spec/unit/client_spec.rb @@ -2,7 +2,7 @@ # Author:: Adam Jacob (<adam@chef.io>) # Author:: Tim Hinderliter (<tim@chef.io>) # Author:: Christopher Walters (<cw@chef.io>) -# Copyright:: Copyright 2008-2016, Chef Software, Inc. +# Copyright:: Copyright 2008-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -71,48 +71,40 @@ describe Chef::Client do describe "configuring output formatters" do context "when no formatter has been configured" do - context "and STDOUT is a TTY" do + it "configures the :doc formatter" do + expect(client.formatters_for_run).to eq([[:doc]]) + end + + context "and force_logger is set" do before do - allow(STDOUT).to receive(:tty?).and_return(true) + Chef::Config[:force_logger] = true end - it "configures the :doc formatter" do - expect(client.formatters_for_run).to eq([[:doc]]) + it "configures the :null formatter" do + expect(client.formatters_for_run).to eq([[:null]]) end + end - context "and force_logger is set" do - before do - Chef::Config[:force_logger] = true - end - - it "configures the :null formatter" do - expect(Chef::Config[:force_logger]).to be_truthy - expect(client.formatters_for_run).to eq([[:null]]) - end - + context "and force_formatter is set" do + before do + Chef::Config[:force_formatter] = true end + it "configures the :doc formatter" do + expect(client.formatters_for_run).to eq([[:doc]]) + end end - context "and STDOUT is not a TTY" do + context "both are set" do before do - allow(STDOUT).to receive(:tty?).and_return(false) + Chef::Config[:force_formatter] = true + Chef::Config[:force_logger] = true end - it "configures the :null formatter" do - expect(client.formatters_for_run).to eq([[:null]]) - end - - context "and force_formatter is set" do - before do - Chef::Config[:force_formatter] = true - end - it "it configures the :doc formatter" do - expect(client.formatters_for_run).to eq([[:doc]]) - end + it "configures the :doc formatter" do + expect(client.formatters_for_run).to eq([[:doc]]) end end - end context "when a formatter is configured" do @@ -188,7 +180,7 @@ describe Chef::Client do # ---Client#sync_cookbooks -- downloads the list of cookbooks to sync # expect_any_instance_of(Chef::CookbookSynchronizer).to receive(:sync_cookbooks) - expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_cookbook_sync) + expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_url], version_class: Chef::CookbookManifestVersions).and_return(http_cookbook_sync) expect(http_cookbook_sync).to receive(:post). with("environments/_default/cookbook_versions", { :run_list => ["override_recipe"] }). and_return({}) @@ -222,7 +214,7 @@ describe Chef::Client do # ---Client#sync_cookbooks -- downloads the list of cookbooks to sync # expect_any_instance_of(Chef::CookbookSynchronizer).to receive(:sync_cookbooks) - expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_cookbook_sync) + expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_url], version_class: Chef::CookbookManifestVersions).and_return(http_cookbook_sync) expect(http_cookbook_sync).to receive(:post). with("environments/_default/cookbook_versions", { :run_list => ["new_run_list_recipe"] }). and_return({}) @@ -402,6 +394,55 @@ describe Chef::Client do end end + describe "load_required_recipe" do + let(:rest) { double("Chef::ServerAPI (required recipe)") } + let(:run_context) { double("Chef::RunContext") } + let(:recipe) { double("Chef::Recipe (required recipe)") } + let(:required_recipe) do + <<EOM +fake_recipe_variable = "for reals" +EOM + end + + context "when required_recipe is configured" do + + before(:each) do + expect(rest).to receive(:get).with("required_recipe").and_return(required_recipe) + expect(Chef::Recipe).to receive(:new).with(nil, nil, run_context).and_return(recipe) + expect(recipe).to receive(:from_file) + end + + it "fetches the recipe and adds it to the run context" do + client.load_required_recipe(rest, run_context) + end + + context "when the required_recipe has bad contents" do + let(:required_recipe) do + <<EOM +this is not a recipe +EOM + end + it "should not raise an error" do + expect { client.load_required_recipe(rest, run_context) }.not_to raise_error() + end + end + end + + context "when required_recipe returns 404" do + let(:http_response) { Net::HTTPNotFound.new("1.1", "404", "Not Found") } + let(:http_exception) { Net::HTTPServerException.new('404 "Not Found"', http_response) } + + before(:each) do + expect(rest).to receive(:get).with("required_recipe").and_raise(http_exception) + end + + it "should log and continue on" do + expect(Chef::Log).to receive(:debug) + client.load_required_recipe(rest, run_context) + end + end + end + describe "windows_admin_check" do context "platform is not windows" do before do diff --git a/spec/unit/config_fetcher_spec.rb b/spec/unit/config_fetcher_spec.rb index 6847ee5fd3..a674d4de33 100644 --- a/spec/unit/config_fetcher_spec.rb +++ b/spec/unit/config_fetcher_spec.rb @@ -58,7 +58,7 @@ describe Chef::ConfigFetcher do and_return(invalid_json) expect(Chef::Application).to receive(:fatal!). - with(invalid_json_error_regex, Chef::Exceptions::DeprecatedExitCode.new) + with(invalid_json_error_regex) fetcher.fetch_json end end @@ -104,7 +104,7 @@ describe Chef::ConfigFetcher do with("").and_return(invalid_json) expect(Chef::Application).to receive(:fatal!). - with(invalid_json_error_regex, Chef::Exceptions::DeprecatedExitCode.new) + with(invalid_json_error_regex) fetcher.fetch_json end end diff --git a/spec/unit/cookbook/cookbook_version_loader_spec.rb b/spec/unit/cookbook/cookbook_version_loader_spec.rb index 786e17f35b..40a054abee 100644 --- a/spec/unit/cookbook/cookbook_version_loader_spec.rb +++ b/spec/unit/cookbook/cookbook_version_loader_spec.rb @@ -40,42 +40,45 @@ describe Chef::Cookbook::CookbookVersionLoader do File.join(cookbook_path, cookbook_relative_path) end + def full_paths_for_part(part) + loaded_cookbook.files_for(part).inject([]) { |memo, f| memo << f[:full_path]; memo } + end + it "loads attribute files of the cookbook" do - expect(loaded_cookbook.attribute_filenames).to include(full_path("/attributes/default.rb")) - expect(loaded_cookbook.attribute_filenames).to include(full_path("/attributes/smokey.rb")) + expect(full_paths_for_part("attributes")).to include(full_path("/attributes/default.rb")) + expect(full_paths_for_part("attributes")).to include(full_path("/attributes/smokey.rb")) end it "loads definition files" do - expect(loaded_cookbook.definition_filenames).to include(full_path("/definitions/client.rb")) - expect(loaded_cookbook.definition_filenames).to include(full_path("/definitions/server.rb")) + expect(full_paths_for_part("definitions")).to include(full_path("/definitions/client.rb")) + expect(full_paths_for_part("definitions")).to include(full_path("/definitions/server.rb")) end it "loads recipes" do - expect(loaded_cookbook.recipe_filenames).to include(full_path("/recipes/default.rb")) - expect(loaded_cookbook.recipe_filenames).to include(full_path("/recipes/gigantor.rb")) - expect(loaded_cookbook.recipe_filenames).to include(full_path("/recipes/one.rb")) - expect(loaded_cookbook.recipe_filenames).to include(full_path("/recipes/return.rb")) + expect(full_paths_for_part("recipes")).to include(full_path("/recipes/default.rb")) + expect(full_paths_for_part("recipes")).to include(full_path("/recipes/gigantor.rb")) + expect(full_paths_for_part("recipes")).to include(full_path("/recipes/one.rb")) + expect(full_paths_for_part("recipes")).to include(full_path("/recipes/return.rb")) end it "loads libraries" do - expect(loaded_cookbook.library_filenames).to include(full_path("/libraries/openldap.rb")) - expect(loaded_cookbook.library_filenames).to include(full_path("/libraries/openldap/version.rb")) + expect(full_paths_for_part("libraries")).to include(full_path("/libraries/openldap.rb")) + expect(full_paths_for_part("libraries")).to include(full_path("/libraries/openldap/version.rb")) end it "loads static files in the files/ dir" do - expect(loaded_cookbook.file_filenames).to include(full_path("/files/default/remotedir/remotesubdir/remote_subdir_file1.txt")) - expect(loaded_cookbook.file_filenames).to include(full_path("/files/default/remotedir/remotesubdir/remote_subdir_file2.txt")) + expect(full_paths_for_part("files")).to include(full_path("/files/default/remotedir/remotesubdir/remote_subdir_file1.txt")) + expect(full_paths_for_part("files")).to include(full_path("/files/default/remotedir/remotesubdir/remote_subdir_file2.txt")) end it "loads files that start with a ." do - expect(loaded_cookbook.file_filenames).to include(full_path("/files/default/.dotfile")) - expect(loaded_cookbook.file_filenames).to include(full_path("/files/default/.ssh/id_rsa")) - expect(loaded_cookbook.file_filenames).to include(full_path("/files/default/remotedir/.a_dotdir/.a_dotfile_in_a_dotdir")) + expect(full_paths_for_part("files")).to include(full_path("/files/default/.dotfile")) + expect(full_paths_for_part("files")).to include(full_path("/files/default/.ssh/id_rsa")) + expect(full_paths_for_part("files")).to include(full_path("/files/default/remotedir/.a_dotdir/.a_dotfile_in_a_dotdir")) end it "loads root files that start with a ." do expect(loaded_cookbook.all_files).to include(full_path(".root_dotfile")) - expect(loaded_cookbook.root_filenames).to include(full_path(".root_dotfile")) end it "loads all unignored files, even if they don't match a segment type" do @@ -97,9 +100,9 @@ describe Chef::Cookbook::CookbookVersionLoader do let(:cookbook_path) { File.join(CHEF_SPEC_DATA, "kitchen/openldap") } it "skips ignored files" do - expect(loaded_cookbook.recipe_filenames).to include(full_path("recipes/gigantor.rb")) - expect(loaded_cookbook.recipe_filenames).to include(full_path("recipes/woot.rb")) - expect(loaded_cookbook.recipe_filenames).to_not include(full_path("recipes/ignoreme.rb")) + expect(full_paths_for_part("recipes")).to include(full_path("recipes/gigantor.rb")) + expect(full_paths_for_part("recipes")).to include(full_path("recipes/woot.rb")) + expect(full_paths_for_part("recipes")).to_not include(full_path("recipes/ignoreme.rb")) end end diff --git a/spec/unit/cookbook/file_vendor_spec.rb b/spec/unit/cookbook/file_vendor_spec.rb index 164fbd8177..557e1b8775 100644 --- a/spec/unit/cookbook/file_vendor_spec.rb +++ b/spec/unit/cookbook/file_vendor_spec.rb @@ -16,6 +16,7 @@ # limitations under the License. # require "spec_helper" +require "chef/cookbook_version" describe Chef::Cookbook::FileVendor do @@ -25,6 +26,12 @@ describe Chef::Cookbook::FileVendor do let(:http) { double("Chef::ServerAPI") } + # A manifest is a Hash of the format defined by Chef::CookbookVersion#manifest + let(:manifest) do + cbv = Chef::CookbookVersion.new("bob", Array(Dir.tmpdir)) + cbv.cookbook_manifest + end + before do file_vendor_class.fetch_from_remote(http) end @@ -39,8 +46,11 @@ describe Chef::Cookbook::FileVendor do context "with a manifest from a cookbook version" do - # A manifest is a Hash of the format defined by Chef::CookbookVersion#manifest - let(:manifest) { { :cookbook_name => "bob", :name => "bob-1.2.3" } } + # # A manifest is a Hash of the format defined by Chef::CookbookVersion#manifest + # let(:manifest) do + # cbv = Chef::CookbookVersion.new("bob", Array(Dir.tmpdir)) + # cbv.cookbook_manifest + # end it "creates a RemoteFileVendor for a given manifest" do file_vendor = file_vendor_class.create_from_manifest(manifest) @@ -53,9 +63,6 @@ describe Chef::Cookbook::FileVendor do context "with a manifest from a cookbook artifact" do - # A manifest is a Hash of the format defined by Chef::CookbookVersion#manifest - let(:manifest) { { :name => "bob" } } - it "creates a RemoteFileVendor for a given manifest" do file_vendor = file_vendor_class.create_from_manifest(manifest) expect(file_vendor).to be_a_kind_of(Chef::Cookbook::RemoteFileVendor) @@ -70,8 +77,10 @@ describe Chef::Cookbook::FileVendor do let(:cookbook_path) { %w{/var/chef/cookbooks /var/chef/other_cookbooks} } - # A manifest is a Hash of the format defined by Chef::CookbookVersion#manifest - let(:manifest) { { :cookbook_name => "bob" } } + let(:manifest) do + cbv = Chef::CookbookVersion.new("bob", Array(Dir.tmpdir)) + cbv.cookbook_manifest + end before do file_vendor_class.fetch_from_disk(cookbook_path) @@ -97,8 +106,10 @@ describe Chef::Cookbook::FileVendor do context "when vendoring a cookbook with a name mismatch" do let(:cookbook_path) { File.join(CHEF_SPEC_DATA, "cookbooks") } - # A manifest is a Hash of the format defined by Chef::CookbookVersion#manifest - let(:manifest) { { :cookbook_name => "name-mismatch" } } + let(:manifest) do + cbv = Chef::CookbookVersion.new("name-mismatch", Array(Dir.tmpdir)) + cbv.cookbook_manifest + end before do file_vendor_class.fetch_from_disk(cookbook_path) diff --git a/spec/unit/cookbook/gem_installer_spec.rb b/spec/unit/cookbook/gem_installer_spec.rb new file mode 100644 index 0000000000..91e6959331 --- /dev/null +++ b/spec/unit/cookbook/gem_installer_spec.rb @@ -0,0 +1,85 @@ +require "spec_helper" +require "bundler/dsl" + +describe Chef::Cookbook::GemInstaller do + let(:cookbook_collection) do + { + test: double( + :cookbook, + metadata: double( + :metadata, + gems: [["httpclient"], ["nokogiri"]] + ) + ), + test2: double( + :cookbook, + metadata: double( + :metadata, + gems: [["httpclient", ">= 2.0"]] + ) + ), + test3: double( + :cookbook, + metadata: double( + :metadata, + gems: [["httpclient", ">= 1.0"]] + ) + ), + } + end + + let(:gem_installer) do + described_class.new(cookbook_collection, Chef::EventDispatch::Dispatcher.new) + end + + let(:gemfile) do + StringIO.new + end + + let(:shell_out) do + double(:shell_out, stdout: "") + end + + let(:bundler_dsl) do + b = Bundler::Dsl.new + b.instance_eval(gemfile.string) + b + end + + before(:each) do + # Prepare mocks: using a StringIO instead of a File + expect(Dir).to receive(:mktmpdir).and_yield("") + expect(File).to receive(:open).and_yield(gemfile) + expect(gemfile).to receive(:path).and_return("") + expect(IO).to receive(:read).and_return("") + expect(gem_installer).to receive(:shell_out!).and_return(shell_out) + + end + + it "generates a valid Gemfile" do + expect { gem_installer.install }.to_not raise_error + + expect { bundler_dsl }.to_not raise_error + end + + it "generate a Gemfile with all constraints" do + expect { gem_installer.install }.to_not raise_error + + expect(bundler_dsl.dependencies.find { |d| d.name == "httpclient" }.requirements_list.length).to eql(2) + end + + it "generates a valid Gemfile when Chef::Config[:rubygems_url] is set to a String" do + Chef::Config[:rubygems_url] = "https://www.rubygems.org" + expect { gem_installer.install }.to_not raise_error + + expect(bundler_dsl.dependencies.find { |d| d.name == "httpclient" }.requirements_list.length).to eql(2) + end + + it "generates a valid Gemfile when Chef::Config[:rubygems_url] is set to an Array" do + Chef::Config[:rubygems_url] = [ "https://www.rubygems.org" ] + + expect { gem_installer.install }.to_not raise_error + + expect(bundler_dsl.dependencies.find { |d| d.name == "httpclient" }.requirements_list.length).to eql(2) + end +end diff --git a/spec/unit/cookbook/manifest_v0_spec.rb b/spec/unit/cookbook/manifest_v0_spec.rb new file mode 100644 index 0000000000..0f5cfbe7a4 --- /dev/null +++ b/spec/unit/cookbook/manifest_v0_spec.rb @@ -0,0 +1,133 @@ +# +# Copyright:: Copyright 2017, 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/cookbook_manifest" +require "chef/digester" +require "pathname" + +describe Chef::Cookbook::ManifestV0 do + let(:version) { "1.2.3" } + + let(:identifier) { "9e10455ce2b4a4e29424b7064b1d67a1a25c9d3b" } + + let(:metadata) do + Chef::Cookbook::Metadata.new.tap do |m| + m.version(version) + end + end + + let(:cookbook_root) { "/tmp/blah" } + + let(:cookbook_version) do + Chef::CookbookVersion.new("tatft", cookbook_root).tap do |c| + c.metadata = metadata + c.identifier = identifier + end + end + + let(:cookbook_manifest) { Chef::CookbookManifest.new(cookbook_version) } + + let(:cookbook_root) { File.join(CHEF_SPEC_DATA, "cb_version_cookbooks", "tatft") } + + let(:all_files) { Dir[File.join(cookbook_root, "**", "**")].reject { |f| File.directory? f } } + + let(:expected_hash) do + { + "attributes" => [{ "name" => "default.rb", "path" => "attributes/default.rb", "checksum" => "a88697db56181498a8828d5531271ad9", "specificity" => "default" }], + "chef_type" => "cookbook_version", + "cookbook_name" => "tatft", + "definitions" => [{ "name" => "runit_service.rb", "path" => "definitions/runit_service.rb", "checksum" => "c40cf9b4c6eb15a8e49e31602f701161", "specificity" => "default" }], + "files" => [{ "name" => "giant_blob.tgz", "path" => "files/default/giant_blob.tgz", "checksum" => "5b4b194bb80938bb18da7af5c823cb1b", "specificity" => "default" }], + "frozen?" => false, + "libraries" => [{ "name" => "ownage.rb", "path" => "libraries/ownage.rb", "checksum" => "4686edd9968909034692e09e058d90d9", "specificity" => "default" }], + "name" => "tatft-1.2.3", + "providers" => [{ "name" => "lwp.rb", "path" => "providers/lwp.rb", "checksum" => "bc189d68f77bb054d1070aeff7669557", "specificity" => "default" }], + "recipes" => [{ "name" => "default.rb", "path" => "recipes/default.rb", "checksum" => "09bc749f00c68717d288de9c8d7c644f", "specificity" => "default" }], + "resources" => [{ "name" => "lwr.rb", "path" => "resources/lwr.rb", "checksum" => "609c40d3d3f269e7edf230277a240ef5", "specificity" => "default" }], + "root_files" => [{ "name" => "README.rdoc", "path" => "README.rdoc", "checksum" => "cd7be9a1b9b1f33e3bcd9c3f4bc8dde5", "specificity" => "default" }], + "templates" => [{ "name" => "configuration.erb", "path" => "templates/default/configuration.erb", "checksum" => "d41d8cd98f00b204e9800998ecf8427e", "specificity" => "default" }], + "version" => "1.2.3", + } + end + + describe "#from_hash" do + let(:source_hash) do + { + "attributes" => [{ "name" => "default.rb", "path" => "attributes/default.rb", "checksum" => "a88697db56181498a8828d5531271ad9", "specificity" => "default" }], + "recipes" => [{ "name" => "default.rb", "path" => "recipes/default.rb", "checksum" => "09bc749f00c68717d288de9c8d7c644f", "specificity" => "default" }], + "root_files" => [{ "name" => "README.rdoc", "path" => "README.rdoc", "checksum" => "cd7be9a1b9b1f33e3bcd9c3f4bc8dde5", "specificity" => "default" }], + "name" => "tatft-1.2.3", + "version" => "1.2.3", + } + end + + it "preserves the version" do + result = described_class.from_hash(source_hash) + expect(result["version"]).to eq "1.2.3" + end + + it "creates an all_files key and populates it" do + result = described_class.from_hash(source_hash) + expect(result[:all_files].map { |f| f["name"] }).to match_array %w{ recipes/default.rb attributes/default.rb README.rdoc } + end + + it "deletes unwanted segment types" do + result = described_class.from_hash(source_hash) + expect(result["attributes"]).to be_nil + end + + it "preserves frozeness" do + source_hash["frozen?"] = true + result = described_class.from_hash(source_hash) + expect(result["frozen?"]).to be true + end + end + + describe "#to_hash" do + it "accepts a cookbook manifest" do + result = described_class.to_hash(cookbook_manifest) + expect(result).to be_a(Hash) + end + + it "preserves frozeness" do + cookbook_version.freeze_version + expect(described_class.to_hash(cookbook_manifest)["frozen?"]).to be true + end + end + + context "ensures that all segments exist" do + Chef::Cookbook::ManifestV0::COOKBOOK_SEGMENTS.each do |segment| + it "with #{segment}" do + result = described_class.to_hash(cookbook_manifest) + expect(result[segment]).to be_empty + end + end + end + + context "when given a cookbook with some files" do + before do + cookbook_version.all_files = all_files + end + + Chef::Cookbook::ManifestV0::COOKBOOK_SEGMENTS.each do |segment| + it "places the files for #{segment} correctly" do + result = described_class.to_hash(cookbook_manifest) + expect(result[segment]).to eq(expected_hash[segment]) + end + end + end +end diff --git a/spec/unit/cookbook/manifest_v2_spec.rb b/spec/unit/cookbook/manifest_v2_spec.rb new file mode 100644 index 0000000000..23df950f4a --- /dev/null +++ b/spec/unit/cookbook/manifest_v2_spec.rb @@ -0,0 +1,70 @@ +# +# Copyright:: Copyright 2017, 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/cookbook_manifest" +require "chef/digester" +require "pathname" + +describe Chef::Cookbook::ManifestV2 do + let(:version) { "1.2.3" } + + let(:identifier) { "9e10455ce2b4a4e29424b7064b1d67a1a25c9d3b" } + + let(:metadata) do + Chef::Cookbook::Metadata.new.tap do |m| + m.version(version) + end + end + + let(:cookbook_root) { "/tmp/blah" } + + let(:cookbook_version) do + Chef::CookbookVersion.new("tatft", cookbook_root).tap do |c| + c.metadata = metadata + c.identifier = identifier + end + end + + let(:cookbook_manifest) { Chef::CookbookManifest.new(cookbook_version) } + + let(:cookbook_root) { File.join(CHEF_SPEC_DATA, "cb_version_cookbooks", "tatft") } + + let(:all_files) { Dir[File.join(cookbook_root, "**", "**")].reject { |f| File.directory? f } } + + describe "#to_hash" do + it "accepts a cookbook manifest" do + result = described_class.to_hash(cookbook_manifest) + expect(result).to be_a(Hash) + end + + it "preserves frozeness" do + cookbook_version.freeze_version + expect(described_class.to_hash(cookbook_manifest)["frozen?"]).to be true + end + end + + context "when given a cookbook with some files" do + before do + cookbook_version.all_files = all_files + end + + it "populates all_files correctly" do + result = described_class.to_hash(cookbook_manifest) + expect(result["all_files"][0]).not_to include(:full_path) + end + end +end diff --git a/spec/unit/cookbook/metadata_spec.rb b/spec/unit/cookbook/metadata_spec.rb index d1117127f1..2fdc43c289 100644 --- a/spec/unit/cookbook/metadata_spec.rb +++ b/spec/unit/cookbook/metadata_spec.rb @@ -28,8 +28,7 @@ describe Chef::Cookbook::Metadata do before do @fields = [ :name, :description, :long_description, :maintainer, :maintainer_email, :license, :platforms, :dependencies, - :recommendations, :suggestions, :conflicting, :providing, - :replacing, :attributes, :groupings, :recipes, :version, + :providing, :attributes, :recipes, :version, :source_url, :issues_url, :privacy, :ohai_versions, :chef_versions, :gems ] end @@ -114,30 +113,10 @@ describe Chef::Cookbook::Metadata do expect(metadata.dependencies).to eq(Mash.new) end - it "has an empty recommends list" do - expect(metadata.recommendations).to eq(Mash.new) - end - - it "has an empty suggestions list" do - expect(metadata.suggestions).to eq(Mash.new) - end - - it "has an empty conflicts list" do - expect(metadata.conflicting).to eq(Mash.new) - end - - it "has an empty replaces list" do - expect(metadata.replacing).to eq(Mash.new) - end - it "has an empty attributes list" do expect(metadata.attributes).to eq(Mash.new) end - it "has an empty groupings list" do - expect(metadata.groupings).to eq(Mash.new) - end - it "has an empty recipes list" do expect(metadata.recipes).to eq(Mash.new) end @@ -234,11 +213,7 @@ describe Chef::Cookbook::Metadata do dep_types = { :depends => [ :dependencies, "foo::bar", "> 0.2" ], - :recommends => [ :recommendations, "foo::bar", ">= 0.2" ], - :suggests => [ :suggestions, "foo::bar", "> 0.2" ], - :conflicts => [ :conflicting, "foo::bar", "~> 0.2" ], :provides => [ :providing, "foo::bar", "<= 0.2" ], - :replaces => [ :replacing, "foo::bar", "= 0.2.1" ], } dep_types.sort_by(&:to_s).each do |dep, dep_args| check_with = dep_args.shift @@ -255,11 +230,7 @@ describe Chef::Cookbook::Metadata do dep_types = { :depends => [ :dependencies, "foo::bar", ">0.2", "> 0.2" ], - :recommends => [ :recommendations, "foo::bar", ">=0.2", ">= 0.2" ], - :suggests => [ :suggestions, "foo::bar", ">0.2", "> 0.2" ], - :conflicts => [ :conflicting, "foo::bar", "~>0.2", "~> 0.2" ], :provides => [ :providing, "foo::bar", "<=0.2", "<= 0.2" ], - :replaces => [ :replacing, "foo::bar", "=0.2.1", "= 0.2.1" ], } dep_types.sort_by(&:to_s).each do |dep, dep_args| check_with = dep_args.shift @@ -278,11 +249,7 @@ describe Chef::Cookbook::Metadata do describe "in the obsoleted format" do dep_types = { :depends => [ "foo::bar", "> 0.2", "< 1.0" ], - :recommends => [ "foo::bar", ">= 0.2", "< 1.0" ], - :suggests => [ "foo::bar", "> 0.2", "< 1.0" ], - :conflicts => [ "foo::bar", "> 0.2", "< 1.0" ], :provides => [ "foo::bar", "> 0.2", "< 1.0" ], - :replaces => [ "foo::bar", "> 0.2.1", "< 1.0" ], } dep_types.each do |dep, dep_args| @@ -295,11 +262,7 @@ describe Chef::Cookbook::Metadata do describe "with obsolete operators" do dep_types = { :depends => [ "foo::bar", ">> 0.2"], - :recommends => [ "foo::bar", ">> 0.2"], - :suggests => [ "foo::bar", ">> 0.2"], - :conflicts => [ "foo::bar", ">> 0.2"], :provides => [ "foo::bar", ">> 0.2"], - :replaces => [ "foo::bar", ">> 0.2.1"], } dep_types.each do |dep, dep_args| @@ -452,33 +415,6 @@ describe Chef::Cookbook::Metadata do end end - describe "attribute groupings" do - it "should allow you set a grouping" do - group = { - "title" => "MySQL Tuning", - "description" => "Setting from the my.cnf file that allow you to tune your mysql server", - } - expect(metadata.grouping("/db/mysql/databases/tuning", group)).to eq(group) - end - it "should not accept anything but a string for display_name" do - expect do - metadata.grouping("db/mysql/databases", :title => "foo") - end.not_to raise_error - expect do - metadata.grouping("db/mysql/databases", :title => Hash.new) - end.to raise_error(ArgumentError) - end - - it "should not accept anything but a string for the description" do - expect do - metadata.grouping("db/mysql/databases", :description => "foo") - end.not_to raise_error - expect do - metadata.grouping("db/mysql/databases", :description => Hash.new) - end.to raise_error(ArgumentError) - end - end - describe "cookbook attributes" do it "should allow you set an attributes metadata" do attrs = { @@ -765,7 +701,10 @@ describe Chef::Cookbook::Metadata do describe "recipes" do let(:cookbook) do c = Chef::CookbookVersion.new("test_cookbook") - c.recipe_files = [ "default.rb", "enlighten.rb" ] + c.manifest = { all_files: [ + { name: "recipes/default.rb", path: "recipes/default.rb", checksum: "my_only_friend" }, + { name: "recipes/enlighten.rb", path: "recipes/enlighten.rb", checksum: "my_only_friend" }, + ] } c end @@ -801,11 +740,7 @@ describe Chef::Cookbook::Metadata do metadata.depends "bobo", "= 1.0" metadata.depends "bubu", "=1.0" metadata.depends "bobotclown", "= 1.1" - metadata.recommends "snark", "< 3.0" - metadata.suggests "kindness", "> 2.0" - metadata.conflicts "hatred" metadata.provides "foo(:bar, :baz)" - metadata.replaces "snarkitron" metadata.recipe "test_cookbook::enlighten", "is your buddy" metadata.attribute "bizspark/has_login", :display_name => "You have nothing" @@ -840,11 +775,7 @@ describe Chef::Cookbook::Metadata do license platforms dependencies - suggestions - recommendations - conflicting providing - replacing attributes recipes version @@ -885,11 +816,7 @@ describe Chef::Cookbook::Metadata do license platforms dependencies - suggestions - recommendations - conflicting providing - replacing attributes recipes version @@ -911,40 +838,34 @@ describe Chef::Cookbook::Metadata do @hash = metadata.to_hash end - [:dependencies, - :recommendations, - :suggestions, - :conflicting, - :replacing].each do |to_check| - it "should transform deprecated greater than syntax for :#{to_check}" do - @hash[to_check.to_s]["foo::bar"] = ">> 0.2" - deserial = Chef::Cookbook::Metadata.from_hash(@hash) - expect(deserial.send(to_check)["foo::bar"]).to eq("> 0.2") - end + it "should transform deprecated greater than syntax for :dependencies" do + @hash[:dependencies.to_s]["foo::bar"] = ">> 0.2" + deserial = Chef::Cookbook::Metadata.from_hash(@hash) + expect(deserial.send(:dependencies)["foo::bar"]).to eq("> 0.2") + end - it "should transform deprecated less than syntax for :#{to_check}" do - @hash[to_check.to_s]["foo::bar"] = "<< 0.2" - deserial = Chef::Cookbook::Metadata.from_hash(@hash) - expect(deserial.send(to_check)["foo::bar"]).to eq("< 0.2") - end + it "should transform deprecated less than syntax for :dependencies" do + @hash[:dependencies.to_s]["foo::bar"] = "<< 0.2" + deserial = Chef::Cookbook::Metadata.from_hash(@hash) + expect(deserial.send(:dependencies)["foo::bar"]).to eq("< 0.2") + end - it "should ignore multiple dependency constraints for :#{to_check}" do - @hash[to_check.to_s]["foo::bar"] = [ ">= 1.0", "<= 5.2" ] - deserial = Chef::Cookbook::Metadata.from_hash(@hash) - expect(deserial.send(to_check)["foo::bar"]).to eq([]) - end + it "should ignore multiple dependency constraints for :dependencies" do + @hash[:dependencies.to_s]["foo::bar"] = [ ">= 1.0", "<= 5.2" ] + deserial = Chef::Cookbook::Metadata.from_hash(@hash) + expect(deserial.send(:dependencies)["foo::bar"]).to eq([]) + end - it "should accept an empty array of dependency constraints for :#{to_check}" do - @hash[to_check.to_s]["foo::bar"] = [] - deserial = Chef::Cookbook::Metadata.from_hash(@hash) - expect(deserial.send(to_check)["foo::bar"]).to eq([]) - end + it "should accept an empty array of dependency constraints for :dependencies" do + @hash[:dependencies.to_s]["foo::bar"] = [] + deserial = Chef::Cookbook::Metadata.from_hash(@hash) + expect(deserial.send(:dependencies)["foo::bar"]).to eq([]) + end - it "should accept single-element arrays of dependency constraints for :#{to_check}" do - @hash[to_check.to_s]["foo::bar"] = [ ">= 2.0" ] - deserial = Chef::Cookbook::Metadata.from_hash(@hash) - expect(deserial.send(to_check)["foo::bar"]).to eq(">= 2.0") - end + it "should accept single-element arrays of dependency constraints for :dependencies" do + @hash[:dependencies.to_s]["foo::bar"] = [ ">= 2.0" ] + deserial = Chef::Cookbook::Metadata.from_hash(@hash) + expect(deserial.send(:dependencies)["foo::bar"]).to eq(">= 2.0") end end diff --git a/spec/unit/cookbook/synchronizer_spec.rb b/spec/unit/cookbook/synchronizer_spec.rb index 82876273e7..77e64482da 100644 --- a/spec/unit/cookbook/synchronizer_spec.rb +++ b/spec/unit/cookbook/synchronizer_spec.rb @@ -62,6 +62,7 @@ describe Chef::CookbookSynchronizer do let(:cookbook_a_default_recipe) do { "path" => "recipes/default.rb", + "name" => "recipes/default.rb", "url" => "http://chef.example.com/abc123", "checksum" => "abc123", } @@ -70,6 +71,7 @@ describe Chef::CookbookSynchronizer do let(:cookbook_a_default_attrs) do { "path" => "attributes/default.rb", + "name" => "attributes/default.rb", "url" => "http://chef.example.com/abc456", "checksum" => "abc456", } @@ -78,6 +80,7 @@ describe Chef::CookbookSynchronizer do let(:cookbook_a_template) do { "path" => "templates/default/apache2.conf.erb", + "name" => "templates/apache2.conf.erb", "url" => "http://chef.example.com/ffffff", "checksum" => "abc125", } @@ -86,18 +89,14 @@ describe Chef::CookbookSynchronizer do let(:cookbook_a_file) do { "path" => "files/default/megaman.conf", + "name" => "files/megaman.conf", "url" => "http://chef.example.com/megaman.conf", "checksum" => "abc124", } end let(:cookbook_a_manifest) do - segments = [ :resources, :providers, :recipes, :definitions, :libraries, :attributes, :files, :templates, :root_files ] - cookbook_a_manifest = segments.inject({}) { |h, segment| h[segment.to_s] = []; h } - cookbook_a_manifest["recipes"] = [ cookbook_a_default_recipe ] - cookbook_a_manifest["attributes"] = [ cookbook_a_default_attrs ] - cookbook_a_manifest["templates"] = [ cookbook_a_template ] - cookbook_a_manifest["files"] = [ cookbook_a_file ] + cookbook_a_manifest = { all_files: [ cookbook_a_default_recipe, cookbook_a_default_attrs, cookbook_a_template, cookbook_a_file ] } cookbook_a_manifest end diff --git a/spec/unit/cookbook/syntax_check_spec.rb b/spec/unit/cookbook/syntax_check_spec.rb index aa6fe49eb9..a24fa3ab2a 100644 --- a/spec/unit/cookbook/syntax_check_spec.rb +++ b/spec/unit/cookbook/syntax_check_spec.rb @@ -60,6 +60,7 @@ describe Chef::Cookbook::SyntaxCheck do openldap_stuff.conf.erb nested_openldap_partials.erb nested_partial.erb + openldap_nested_variable_stuff.erb openldap_variable_stuff.conf.erb test.erb some_windows_line_endings.erb diff --git a/spec/unit/cookbook_loader_spec.rb b/spec/unit/cookbook_loader_spec.rb index eef5d2afd5..a5fb622da0 100644 --- a/spec/unit/cookbook_loader_spec.rb +++ b/spec/unit/cookbook_loader_spec.rb @@ -31,6 +31,10 @@ describe Chef::CookbookLoader do let(:cookbook_loader) { Chef::CookbookLoader.new(repo_paths) } + def full_paths_for_part(cb, part) + cookbook_loader[cb].files_for(part).inject([]) { |memo, f| memo << f[:full_path]; memo } + end + it "checks each directory only once when loading (CHEF-3487)" do cookbook_paths = [] repo_paths.each do |repo_path| @@ -95,13 +99,7 @@ describe Chef::CookbookLoader do cookbook_loader.each do |cookbook_name, cookbook| seen << cookbook_name end - expect(seen[0]).to eq("angrybash") - expect(seen[1]).to eq("apache2") - expect(seen[2]).to eq("borken") - expect(seen[3]).to eq("ignorken") - expect(seen[4]).to eq("java") - expect(seen[5]).to eq("name-mismatch") - expect(seen[6]).to eq("openldap") + expect(seen).to eq %w{angrybash apache2 borken ignorken java name-mismatch openldap preseed supports-platform-constraints} end end @@ -112,61 +110,61 @@ describe Chef::CookbookLoader do end it "should allow you to override an attribute file via cookbook_path" do - expect(cookbook_loader[:openldap].attribute_filenames.detect do |f| + expect(full_paths_for_part(:openldap, "attributes").detect do |f| f =~ /cookbooks\/openldap\/attributes\/default.rb/ end).not_to eql(nil) - expect(cookbook_loader[:openldap].attribute_filenames.detect do |f| + expect(full_paths_for_part(:openldap, "attributes").detect do |f| f =~ /kitchen\/openldap\/attributes\/default.rb/ end).to eql(nil) end it "should load different attribute files from deeper paths" do - expect(cookbook_loader[:openldap].attribute_filenames.detect do |f| + expect(full_paths_for_part(:openldap, "attributes").detect do |f| f =~ /kitchen\/openldap\/attributes\/robinson.rb/ end).not_to eql(nil) end it "should allow you to override a definition file via cookbook_path" do - expect(cookbook_loader[:openldap].definition_filenames.detect do |f| + expect(full_paths_for_part(:openldap, "definitions").detect do |f| f =~ /cookbooks\/openldap\/definitions\/client.rb/ end).not_to eql(nil) - expect(cookbook_loader[:openldap].definition_filenames.detect do |f| + expect(full_paths_for_part(:openldap, "definitions").detect do |f| f =~ /kitchen\/openldap\/definitions\/client.rb/ end).to eql(nil) end it "should load definition files from deeper paths" do - expect(cookbook_loader[:openldap].definition_filenames.detect do |f| + expect(full_paths_for_part(:openldap, "definitions").detect do |f| f =~ /kitchen\/openldap\/definitions\/drewbarrymore.rb/ end).not_to eql(nil) end it "should allow you to override a recipe file via cookbook_path" do - expect(cookbook_loader[:openldap].recipe_filenames.detect do |f| + expect(full_paths_for_part(:openldap, "recipes").detect do |f| f =~ /cookbooks\/openldap\/recipes\/gigantor.rb/ end).not_to eql(nil) - expect(cookbook_loader[:openldap].recipe_filenames.detect do |f| + expect(full_paths_for_part(:openldap, "recipes").detect do |f| f =~ /kitchen\/openldap\/recipes\/gigantor.rb/ end).to eql(nil) end it "should load recipe files from deeper paths" do - expect(cookbook_loader[:openldap].recipe_filenames.detect do |f| + expect(full_paths_for_part(:openldap, "recipes").detect do |f| f =~ /kitchen\/openldap\/recipes\/woot.rb/ end).not_to eql(nil) end it "should allow you to have an 'ignore' file, which skips loading files in later cookbooks" do - expect(cookbook_loader[:openldap].recipe_filenames.detect do |f| + expect(full_paths_for_part(:openldap, "recipes").detect do |f| f =~ /kitchen\/openldap\/recipes\/ignoreme.rb/ end).to eql(nil) end it "should find files that start with a ." do - expect(cookbook_loader[:openldap].file_filenames.detect do |f| + expect(full_paths_for_part(:openldap, "files").detect do |f| f =~ /\.dotfile$/ end).to match(/\.dotfile$/) - expect(cookbook_loader[:openldap].file_filenames.detect do |f| + expect(full_paths_for_part(:openldap, "files").detect do |f| f =~ /\.ssh\/id_rsa$/ end).to match(/\.ssh\/id_rsa$/) end diff --git a/spec/unit/cookbook_manifest_spec.rb b/spec/unit/cookbook_manifest_spec.rb index acf0ade9f9..d77c07e0f7 100644 --- a/spec/unit/cookbook_manifest_spec.rb +++ b/spec/unit/cookbook_manifest_spec.rb @@ -81,11 +81,6 @@ describe Chef::CookbookManifest do expect(cookbook_manifest.frozen_version?).to be(false) end - it "delegates `segment_filenames' to cookbook_version" do - expect(cookbook_version).to receive(:segment_filenames).with(:recipes).and_return([]) - expect(cookbook_manifest.segment_filenames(:recipes)).to eq([]) - end - end context "when given an empty cookbook" do @@ -101,15 +96,7 @@ describe Chef::CookbookManifest do "frozen?" => false, - "recipes" => [], - "definitions" => [], - "libraries" => [], - "attributes" => [], - "files" => [], - "templates" => [], - "resources" => [], - "providers" => [], - "root_files" => [], + "all_files" => [], } end @@ -123,16 +110,7 @@ describe Chef::CookbookManifest do let(:cookbook_root) { File.join(CHEF_SPEC_DATA, "cb_version_cookbooks", "tatft") } - let(:attribute_filenames) { Dir[File.join(cookbook_root, "attributes", "**", "*.rb")] } - let(:definition_filenames) { Dir[File.join(cookbook_root, "definitions", "**", "*.rb")] } - let(:file_filenames) { Dir[File.join(cookbook_root, "files", "**", "*.tgz")] } - let(:recipe_filenames) { Dir[File.join(cookbook_root, "recipes", "**", "*.rb")] } - let(:template_filenames) { Dir[File.join(cookbook_root, "templates", "**", "*.erb")] } - let(:library_filenames) { Dir[File.join(cookbook_root, "libraries", "**", "*.rb")] } - let(:resource_filenames) { Dir[File.join(cookbook_root, "resources", "**", "*.rb")] } - let(:provider_filenames) { Dir[File.join(cookbook_root, "providers", "**", "*.rb")] } - let(:root_filenames) { Array(File.join(cookbook_root, "README.rdoc")) } - let(:metadata_filenames) { Array(File.join(cookbook_root, "metadata.json")) } + let(:all_files) { Dir[File.join(cookbook_root, "**", "**")].reject { |f| File.directory? f } } let(:match_md5) { /[0-9a-f]{32}/ } @@ -141,8 +119,15 @@ describe Chef::CookbookManifest do relative_path = Pathname.new(path).relative_path_from(Pathname.new(cookbook_root)).to_s + parts = relative_path.split("/") + name = if %w{templates files}.include?(parts[0]) && parts.length == 3 + File.join(parts[0], parts[2]) + else + relative_path + end + { - "name" => File.basename(path), + "name" => name, "path" => relative_path, "checksum" => Chef::Digester.generate_md5_checksum_for_file(path), "specificity" => "default", @@ -161,29 +146,12 @@ describe Chef::CookbookManifest do "frozen?" => false, - "recipes" => map_to_file_specs(recipe_filenames), - "definitions" => map_to_file_specs(definition_filenames), - "libraries" => map_to_file_specs(library_filenames), - "attributes" => map_to_file_specs(attribute_filenames), - "files" => map_to_file_specs(file_filenames), - "templates" => map_to_file_specs(template_filenames), - "resources" => map_to_file_specs(resource_filenames), - "providers" => map_to_file_specs(provider_filenames), - "root_files" => map_to_file_specs(root_filenames), + "all_files" => map_to_file_specs(all_files), } end before do - cookbook_version.attribute_filenames = attribute_filenames - cookbook_version.definition_filenames = definition_filenames - cookbook_version.file_filenames = file_filenames - cookbook_version.recipe_filenames = recipe_filenames - cookbook_version.template_filenames = template_filenames - cookbook_version.library_filenames = library_filenames - cookbook_version.resource_filenames = resource_filenames - cookbook_version.provider_filenames = provider_filenames - cookbook_version.root_filenames = root_filenames - cookbook_version.metadata_filenames = metadata_filenames + cookbook_version.all_files = all_files end it "converts the CookbookVersion to a ruby Hash representation" do diff --git a/spec/unit/cookbook_site_streaming_uploader_spec.rb b/spec/unit/cookbook_site_streaming_uploader_spec.rb index 10963386dd..0e9c277b11 100644 --- a/spec/unit/cookbook_site_streaming_uploader_spec.rb +++ b/spec/unit/cookbook_site_streaming_uploader_spec.rb @@ -49,10 +49,6 @@ describe Chef::CookbookSiteStreamingUploader do cookbook = @loader[:openldap] files_count = Dir.glob(File.join(@cookbook_repo, cookbook.name.to_s, "**", "*"), File::FNM_DOTMATCH).count { |file| File.file?(file) } - # The fixture cookbook contains a spec/spec_helper.rb file, which is not - # a part of any cookbook segment, so it is not uploaded. - files_count -= 1 - expect(Tempfile).to receive(:new).with("chef-#{cookbook.name}-build").and_return(FakeTempfile.new("chef-#{cookbook.name}-build")) expect(FileUtils).to receive(:mkdir_p).exactly(files_count + 1).times expect(FileUtils).to receive(:cp).exactly(files_count).times diff --git a/spec/unit/cookbook_spec.rb b/spec/unit/cookbook_spec.rb index 33b4a3ccd8..ac3a1373ea 100644 --- a/spec/unit/cookbook_spec.rb +++ b/spec/unit/cookbook_spec.rb @@ -19,7 +19,8 @@ require "spec_helper" describe Chef::CookbookVersion do -# COOKBOOK_PATH = File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "cookbooks", "openldap")) + COOKBOOK_PATH = File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "cookbooks", "openldap")) + before(:each) do @cookbook_repo = File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "cookbooks")) cl = Chef::CookbookLoader.new(@cookbook_repo) @@ -37,26 +38,21 @@ describe Chef::CookbookVersion do end it "should allow you to set the list of attribute files and create the mapping from short names to paths" do - @cookbook.attribute_filenames = [ "attributes/one.rb", "attributes/two.rb" ] - expect(@cookbook.attribute_filenames).to eq([ "attributes/one.rb", "attributes/two.rb" ]) - expect(@cookbook.attribute_filenames_by_short_filename.keys.sort).to eql(%w{one two}) - expect(@cookbook.attribute_filenames_by_short_filename["one"]).to eq("attributes/one.rb") - expect(@cookbook.attribute_filenames_by_short_filename["two"]).to eq("attributes/two.rb") + expect(@cookbook.attribute_filenames_by_short_filename.keys.sort).to eql(%w{default smokey}) + expect(@cookbook.attribute_filenames_by_short_filename["default"]).to eq(File.join(COOKBOOK_PATH, "attributes/default.rb")) + expect(@cookbook.attribute_filenames_by_short_filename["smokey"]).to eq(File.join(COOKBOOK_PATH, "attributes/smokey.rb")) end it "should allow you to set the list of recipe files and create the mapping of recipe short name to filename" do - @cookbook.recipe_filenames = [ "recipes/one.rb", "recipes/two.rb" ] - expect(@cookbook.recipe_filenames).to eq([ "recipes/one.rb", "recipes/two.rb" ]) - expect(@cookbook.recipe_filenames_by_name.keys.sort).to eql(%w{one two}) - expect(@cookbook.recipe_filenames_by_name["one"]).to eq("recipes/one.rb") - expect(@cookbook.recipe_filenames_by_name["two"]).to eq("recipes/two.rb") + expect(@cookbook.recipe_filenames_by_name.keys.sort).to eql(%w{default gigantor one return}) + expect(@cookbook.recipe_filenames_by_name["one"]).to eq(File.join(COOKBOOK_PATH, "recipes/one.rb")) + expect(@cookbook.recipe_filenames_by_name["gigantor"]).to eq(File.join(COOKBOOK_PATH, "recipes/gigantor.rb")) end it "should generate a list of recipes by fully-qualified name" do - @cookbook.recipe_filenames = [ "recipes/one.rb", "/recipes/two.rb", "three.rb" ] expect(@cookbook.fully_qualified_recipe_names.include?("openldap::one")).to eq(true) - expect(@cookbook.fully_qualified_recipe_names.include?("openldap::two")).to eq(true) - expect(@cookbook.fully_qualified_recipe_names.include?("openldap::three")).to eq(true) + expect(@cookbook.fully_qualified_recipe_names.include?("openldap::gigantor")).to eq(true) + expect(@cookbook.fully_qualified_recipe_names.include?("openldap::return")).to eq(true) end it "should raise an ArgumentException if you try to load a bad recipe name" do diff --git a/spec/unit/cookbook_uploader_spec.rb b/spec/unit/cookbook_uploader_spec.rb index c30df71e34..2c36c2c9c7 100644 --- a/spec/unit/cookbook_uploader_spec.rb +++ b/spec/unit/cookbook_uploader_spec.rb @@ -65,7 +65,7 @@ describe Chef::CookbookUploader do it "creates an HTTP client with default configuration when not initialized with one" do default_http_client = double("Chef::ServerAPI") - expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_url]).and_return(default_http_client) + expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_url], version_class: Chef::CookbookManifestVersions).and_return(default_http_client) uploader = described_class.new(cookbooks_to_upload) expect(uploader.rest).to eq(default_http_client) end diff --git a/spec/unit/cookbook_version_file_specificity_spec.rb b/spec/unit/cookbook_version_file_specificity_spec.rb index 3b5450cb2d..ba7aaa59f5 100644 --- a/spec/unit/cookbook_version_file_specificity_spec.rb +++ b/spec/unit/cookbook_version_file_specificity_spec.rb @@ -23,177 +23,204 @@ describe Chef::CookbookVersion, "file specificity" do before(:each) do @cookbook = Chef::CookbookVersion.new("test-cookbook", "/cookbook-folder") @cookbook.manifest = { - "files" => + "all_files" => [ # afile.rb { - :name => "afile.rb", + :name => "files/afile.rb", :path => "files/host-examplehost.example.org/afile.rb", + :full_path => "/cookbook-folder/files/host-examplehost.example.org/afile.rb", :checksum => "csum-host", :specificity => "host-examplehost.example.org", }, { - :name => "afile.rb", + :name => "files/afile.rb", :path => "files/ubuntu-9.10/afile.rb", + :full_path => "/cookbook-folder/files/ubuntu-9.10/afile.rb", :checksum => "csum-platver-full", :specificity => "ubuntu-9.10", }, { - :name => "afile.rb", + :name => "files/afile.rb", :path => "files/newubuntu-9/afile.rb", + :full_path => "/cookbook-folder/files/newubuntu-9/afile.rb", :checksum => "csum-platver-partial", :specificity => "newubuntu-9", }, { - :name => "afile.rb", + :name => "files/afile.rb", :path => "files/ubuntu/afile.rb", + :full_path => "/cookbook-folder/files/ubuntu/afile.rb", :checksum => "csum-plat", :specificity => "ubuntu", }, { - :name => "afile.rb", + :name => "files/afile.rb", :path => "files/default/afile.rb", + :full_path => "/cookbook-folder/files/default/afile.rb", :checksum => "csum-default", :specificity => "default", }, # for different/odd platform_versions { - :name => "bfile.rb", + :name => "files/bfile.rb", :path => "files/fakeos-2.0.rc.1/bfile.rb", + :full_path => "/cookbook-folder/files/fakeos-2.0.rc.1/bfile.rb", :checksum => "csum2-platver-full", :specificity => "fakeos-2.0.rc.1", }, { - :name => "bfile.rb", + :name => "files/bfile.rb", :path => "files/newfakeos-2.0.rc/bfile.rb", + :full_path => "/cookbook-folder/files/newfakeos-2.0.rc/bfile.rb", :checksum => "csum2-platver-partial", :specificity => "newfakeos-2.0.rc", }, { - :name => "bfile.rb", + :name => "files/bfile.rb", :path => "files/fakeos-maple tree/bfile.rb", + :full_path => "/cookbook-folder/files/fakeos-maple tree/bfile.rb", :checksum => "csum3-platver-full", :specificity => "maple tree", }, { - :name => "bfile.rb", + :name => "files/bfile.rb", :path => "files/fakeos-1/bfile.rb", + :full_path => "/cookbook-folder/files/fakeos-1/bfile.rb", :checksum => "csum4-platver-full", :specificity => "fakeos-1", }, # directory adirectory { - :name => "anotherfile1.rb", + :name => "files/anotherfile1.rb", :path => "files/host-examplehost.example.org/adirectory/anotherfile1.rb.host", + :full_path => "/cookbook-folder/files/host-examplehost.example.org/adirectory/anotherfile1.rb.host", :checksum => "csum-host-1", :specificity => "host-examplehost.example.org", }, { - :name => "anotherfile2.rb", + :name => "files/anotherfile2.rb", :path => "files/host-examplehost.example.org/adirectory/anotherfile2.rb.host", + :full_path => "/cookbook-folder/files/host-examplehost.example.org/adirectory/anotherfile2.rb.host", :checksum => "csum-host-2", :specificity => "host-examplehost.example.org", }, { - :name => "anotherfile1.rb", + :name => "files/anotherfile1.rb", :path => "files/ubuntu-9.10/adirectory/anotherfile1.rb.platform-full-version", + :full_path => "/cookbook-folder/files/ubuntu-9.10/adirectory/anotherfile1.rb.platform-full-version", :checksum => "csum-platver-full-1", :specificity => "ubuntu-9.10", }, { - :name => "anotherfile2.rb", + :name => "files/anotherfile2.rb", :path => "files/ubuntu-9.10/adirectory/anotherfile2.rb.platform-full-version", + :full_path => "/cookbook-folder/files/ubuntu-9.10/adirectory/anotherfile2.rb.platform-full-version", :checksum => "csum-platver-full-2", :specificity => "ubuntu-9.10", }, { - :name => "anotherfile1.rb", + :name => "files/anotherfile1.rb", :path => "files/newubuntu-9/adirectory/anotherfile1.rb.platform-partial-version", + :full_path => "/cookbook-folder/files/newubuntu-9/adirectory/anotherfile1.rb.platform-partial-version", :checksum => "csum-platver-partial-1", :specificity => "newubuntu-9", }, { - :name => "anotherfile2.rb", + :name => "files/anotherfile2.rb", :path => "files/newubuntu-9/adirectory/anotherfile2.rb.platform-partial-version", + :full_path => "/cookbook-folder/files/newubuntu-9/adirectory/anotherfile2.rb.platform-partial-version", :checksum => "csum-platver-partial-2", :specificity => "nweubuntu-9", }, { - :name => "anotherfile1.rb", + :name => "files/anotherfile1.rb", :path => "files/ubuntu/adirectory/anotherfile1.rb.platform", + :full_path => "/cookbook-folder/files/ubuntu/adirectory/anotherfile1.rb.platform", :checksum => "csum-plat-1", :specificity => "ubuntu", }, { - :name => "anotherfile2.rb", + :name => "files/anotherfile2.rb", :path => "files/ubuntu/adirectory/anotherfile2.rb.platform", + :full_path => "/cookbook-folder/files/ubuntu/adirectory/anotherfile2.rb.platform", :checksum => "csum-plat-2", :specificity => "ubuntu", }, { - :name => "anotherfile1.rb", + :name => "files/anotherfile1.rb", :path => "files/default/adirectory/anotherfile1.rb.default", + :full_path => "/cookbook-folder/files/default/adirectory/anotherfile1.rb.default", :checksum => "csum-default-1", :specificity => "default", }, { - :name => "anotherfile2.rb", + :name => "files/anotherfile2.rb", :path => "files/default/adirectory/anotherfile2.rb.default", + :full_path => "/cookbook-folder/files/default/adirectory/anotherfile2.rb.default", :checksum => "csum-default-2", :specificity => "default", }, # for different/odd platform_versions { - :name => "anotherfile1.rb", + :name => "files/anotherfile1.rb", :path => "files/fakeos-2.0.rc.1/adirectory/anotherfile1.rb.platform-full-version", + :full_path => "/cookbook-folder/files/fakeos-2.0.rc.1/adirectory/anotherfile1.rb.platform-full-version", :checksum => "csum2-platver-full-1", :specificity => "fakeos-2.0.rc.1", }, { - :name => "anotherfile2.rb", + :name => "files/anotherfile2.rb", :path => "files/fakeos-2.0.rc.1/adirectory/anotherfile2.rb.platform-full-version", + :full_path => "/cookbook-folder/files/fakeos-2.0.rc.1/adirectory/anotherfile2.rb.platform-full-version", :checksum => "csum2-platver-full-2", :specificity => "fakeos-2.0.rc.1", }, { - :name => "anotherfile1.rb", + :name => "files/anotherfile1.rb", :path => "files/newfakeos-2.0.rc.1/adirectory/anotherfile1.rb.platform-partial-version", + :full_path => "/cookbook-folder/files/newfakeos-2.0.rc.1/adirectory/anotherfile1.rb.platform-partial-version", :checksum => "csum2-platver-partial-1", :specificity => "newfakeos-2.0.rc", }, { - :name => "anotherfile2.rb", + :name => "files/anotherfile2.rb", :path => "files/newfakeos-2.0.rc.1/adirectory/anotherfile2.rb.platform-partial-version", + :full_path => "/cookbook-folder/files/newfakeos-2.0.rc.1/adirectory/anotherfile2.rb.platform-partial-version", :checksum => "csum2-platver-partial-2", :specificity => "newfakeos-2.0.rc", }, { - :name => "anotherfile1.rb", + :name => "files/anotherfile1.rb", :path => "files/fakeos-maple tree/adirectory/anotherfile1.rb.platform-full-version", + :full_path => "/cookbook-folder/files/fakeos-maple tree/adirectory/anotherfile1.rb.platform-full-version", :checksum => "csum3-platver-full-1", :specificity => "fakeos-maple tree", }, { - :name => "anotherfile2.rb", + :name => "files/anotherfile2.rb", :path => "files/fakeos-maple tree/adirectory/anotherfile2.rb.platform-full-version", + :full_path => "/cookbook-folder/files/fakeos-maple tree/adirectory/anotherfile2.rb.platform-full-version", :checksum => "csum3-platver-full-2", :specificity => "fakeos-maple tree", }, { - :name => "anotherfile1.rb", + :name => "files/anotherfile1.rb", :path => "files/fakeos-1/adirectory/anotherfile1.rb.platform-full-version", + :full_path => "/cookbook-folder/files/fakeos-1/adirectory/anotherfile1.rb.platform-full-version", :checksum => "csum4-platver-full-1", :specificity => "fakeos-1", }, { - :name => "anotherfile2.rb", + :name => "files/anotherfile2.rb", :path => "files/fakeos-1/adirectory/anotherfile2.rb.platform-full-version", + :full_path => "/cookbook-folder/files/fakeos-1/adirectory/anotherfile2.rb.platform-full-version", :checksum => "csum4-platver-full-2", :specificity => "fakeos-1", }, diff --git a/spec/unit/cookbook_version_spec.rb b/spec/unit/cookbook_version_spec.rb index 81ea161bfe..83fb3f578f 100644 --- a/spec/unit/cookbook_version_spec.rb +++ b/spec/unit/cookbook_version_spec.rb @@ -25,38 +25,6 @@ describe Chef::CookbookVersion do expect(cookbook_version.name).to eq("tatft") end - it "has no attribute files" do - expect(cookbook_version.attribute_filenames).to be_empty - end - - it "has no resource definition files" do - expect(cookbook_version.definition_filenames).to be_empty - end - - it "has no cookbook files" do - expect(cookbook_version.file_filenames).to be_empty - end - - it "has no recipe files" do - expect(cookbook_version.recipe_filenames).to be_empty - end - - it "has no library files" do - expect(cookbook_version.library_filenames).to be_empty - end - - it "has no LWRP resource files" do - expect(cookbook_version.resource_filenames).to be_empty - end - - it "has no LWRP provider files" do - expect(cookbook_version.provider_filenames).to be_empty - end - - it "has no metadata files" do - expect(cookbook_version.metadata_filenames).to be_empty - end - it "has an empty set of all_files" do expect(cookbook_version.all_files).to be_empty end @@ -82,17 +50,7 @@ describe Chef::CookbookVersion do let(:cookbook_paths_by_type) do { # Dunno if the paths here are representitive of what is set by CookbookLoader... - all_files: Dir[File.join(cookbook_root, "**", "*.rb")], - attribute_filenames: Dir[File.join(cookbook_root, "attributes", "**", "*.rb")], - definition_filenames: Dir[File.join(cookbook_root, "definitions", "**", "*.rb")], - file_filenames: Dir[File.join(cookbook_root, "files", "**", "*.tgz")], - recipe_filenames: Dir[File.join(cookbook_root, "recipes", "**", "*.rb")], - template_filenames: Dir[File.join(cookbook_root, "templates", "**", "*.erb")], - library_filenames: Dir[File.join(cookbook_root, "libraries", "**", "*.rb")], - resource_filenames: Dir[File.join(cookbook_root, "resources", "**", "*.rb")], - provider_filenames: Dir[File.join(cookbook_root, "providers", "**", "*.rb")], - root_filenames: Array(File.join(cookbook_root, "README.rdoc")), - metadata_filenames: Array(File.join(cookbook_root, "metadata.json")), + all_files: Dir[File.join(cookbook_root, "**", "**")], } end @@ -102,18 +60,9 @@ describe Chef::CookbookVersion do let(:cookbook_version) do Chef::CookbookVersion.new("tatft", cookbook_root).tap do |c| - # Currently the cookbook loader finds all the files then tells CookbookVersion - # where they are. - c.attribute_filenames = cookbook_paths_by_type[:attribute_filenames] - c.definition_filenames = cookbook_paths_by_type[:definition_filenames] - c.recipe_filenames = cookbook_paths_by_type[:recipe_filenames] - c.template_filenames = cookbook_paths_by_type[:template_filenames] - c.file_filenames = cookbook_paths_by_type[:file_filenames] - c.library_filenames = cookbook_paths_by_type[:library_filenames] - c.resource_filenames = cookbook_paths_by_type[:resource_filenames] - c.provider_filenames = cookbook_paths_by_type[:provider_filenames] - c.root_filenames = cookbook_paths_by_type[:root_filenames] - c.metadata_filenames = cookbook_paths_by_type[:metadata_filenames] + # Currently the cookbook loader finds all the files then tells CookbookVersion + # where they are. + c.all_files = cookbook_paths_by_type[:all_files] end end @@ -168,18 +117,7 @@ describe Chef::CookbookVersion do let(:cookbook_paths_by_type) do { - # Dunno if the paths here are representitive of what is set by CookbookLoader... - all_files: Dir[File.join(cookbook_root, "**", "*.rb")], - attribute_filenames: Dir[File.join(cookbook_root, "attributes", "**", "*.rb")], - definition_filenames: Dir[File.join(cookbook_root, "definitions", "**", "*.rb")], - file_filenames: Dir[File.join(cookbook_root, "files", "**", "*.*")], - recipe_filenames: Dir[File.join(cookbook_root, "recipes", "**", "*.rb")], - template_filenames: Dir[File.join(cookbook_root, "templates", "**", "*.*")], - library_filenames: Dir[File.join(cookbook_root, "libraries", "**", "*.rb")], - resource_filenames: Dir[File.join(cookbook_root, "resources", "**", "*.rb")], - provider_filenames: Dir[File.join(cookbook_root, "providers", "**", "*.rb")], - root_filenames: Array(File.join(cookbook_root, "README.rdoc")), - metadata_filenames: Array(File.join(cookbook_root, "metadata.json")), + all_files: Dir[File.join(cookbook_root, "**", "**")], } end @@ -187,16 +125,7 @@ describe Chef::CookbookVersion do let(:cookbook_version) do Chef::CookbookVersion.new("cookbook2", cookbook_root).tap do |c| - c.attribute_filenames = cookbook_paths_by_type[:attribute_filenames] - c.definition_filenames = cookbook_paths_by_type[:definition_filenames] - c.recipe_filenames = cookbook_paths_by_type[:recipe_filenames] - c.template_filenames = cookbook_paths_by_type[:template_filenames] - c.file_filenames = cookbook_paths_by_type[:file_filenames] - c.library_filenames = cookbook_paths_by_type[:library_filenames] - c.resource_filenames = cookbook_paths_by_type[:resource_filenames] - c.provider_filenames = cookbook_paths_by_type[:provider_filenames] - c.root_filenames = cookbook_paths_by_type[:root_filenames] - c.metadata_filenames = cookbook_paths_by_type[:metadata_filenames] + c.all_files = cookbook_paths_by_type[:all_files] end end @@ -255,19 +184,19 @@ describe Chef::CookbookVersion do it "should sort based on the version number" do examples = [ - # smaller, larger - ["1.0", "2.0"], - ["1.2.3", "1.2.4"], - ["1.2.3", "1.3.0"], - ["1.2.3", "1.3"], - ["1.2.3", "2.1.1"], - ["1.2.3", "2.1"], - ["1.2", "1.2.4"], - ["1.2", "1.3.0"], - ["1.2", "1.3"], - ["1.2", "2.1.1"], - ["1.2", "2.1"], - ] + # smaller, larger + ["1.0", "2.0"], + ["1.2.3", "1.2.4"], + ["1.2.3", "1.3.0"], + ["1.2.3", "1.3"], + ["1.2.3", "2.1.1"], + ["1.2.3", "2.1"], + ["1.2", "1.2.4"], + ["1.2", "1.3.0"], + ["1.2", "1.3"], + ["1.2", "2.1.1"], + ["1.2", "2.1"], + ] examples.each do |smaller, larger| sm = Chef::CookbookVersion.new("foo", "/tmp/blah") lg = Chef::CookbookVersion.new("foo", "/tmp/blah") @@ -318,42 +247,4 @@ describe Chef::CookbookVersion do end - describe "when deprecation warnings are errors" do - - subject(:cbv) { Chef::CookbookVersion.new("version validation", "/tmp/blah") } - - it "errors on #status and #status=" do - expect { cbv.status = :wat }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) - expect { cbv.status }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) - end - - end - - describe "deprecated features" do - - subject(:cbv) { Chef::CookbookVersion.new("tatft", "/tmp/blah").tap { |c| c.version = "1.2.3" } } - - before do - Chef::Config[:treat_deprecation_warnings_as_errors] = false - end - - it "gives a save URL for the standard cookbook API" do - expect(cbv.save_url).to eq("cookbooks/tatft/1.2.3") - end - - it "gives a force save URL for the standard cookbook API" do - expect(cbv.force_save_url).to eq("cookbooks/tatft/1.2.3?force=true") - end - - it "is \"ready\"" do - # WTF is this? what are the valid states? and why aren't they set with encapsulating methods? - # [Dan 15-Jul-2010] - expect(cbv.status).to eq(:ready) - end - - include_examples "to_json equivalent to Chef::JSONCompat.to_json" do - let(:jsonable) { Chef::CookbookVersion.new("tatft", "/tmp/blah") } - end - - end end diff --git a/spec/unit/data_bag_item_spec.rb b/spec/unit/data_bag_item_spec.rb index e83f0ca0ec..7094a7b1f7 100644 --- a/spec/unit/data_bag_item_spec.rb +++ b/spec/unit/data_bag_item_spec.rb @@ -52,6 +52,10 @@ describe Chef::DataBagItem do expect { data_bag_item.raw_data = { "id" => "octahedron" } }.not_to raise_error end + it "should let you set the raw_data with a hash containing symbols" do + expect { data_bag_item.raw_data = { :id => "octahedron" } }.not_to raise_error + end + it "should let you set the raw_data from a mash" do expect { data_bag_item.raw_data = Mash.new({ "id" => "octahedron" }) }.not_to raise_error end diff --git a/spec/unit/data_collector/messages_spec.rb b/spec/unit/data_collector/messages_spec.rb index 5c6ec8736c..f5df85a988 100644 --- a/spec/unit/data_collector/messages_spec.rb +++ b/spec/unit/data_collector/messages_spec.rb @@ -116,6 +116,141 @@ describe Chef::DataCollector::Messages do deprecations } end + let(:optional_fields) { %w{error policy_group policy_name} } + + before do + allow(run_status).to receive(:exception).and_return(nil) + end + + it "is not missing any required fields" do + missing_fields = required_fields.select do |key| + !Chef::DataCollector::Messages.run_end_message(reporter_data).key?(key) + end + expect(missing_fields).to eq([]) + end + + it "does not have any extra fields" do + extra_fields = Chef::DataCollector::Messages.run_end_message(reporter_data).keys.select do |key| + !required_fields.include?(key) && !optional_fields.include?(key) + end + expect(extra_fields).to eq([]) + end + + it "only includes updated resources in its count" do + message = Chef::DataCollector::Messages.run_end_message(reporter_data) + expect(message["total_resource_count"]).to eq(2) + expect(message["updated_resource_count"]).to eq(1) + end + end + + context "when the run was not successful" do + let(:required_fields) do + %w{ + chef_server_fqdn + entity_uuid + id + end_time + error + expanded_run_list + message_type + message_version + node + node_name + organization_name + resources + run_id + run_list + source + start_time + status + total_resource_count + updated_resource_count + deprecations + } + end + let(:optional_fields) { %w{policy_group policy_name} } + + before do + allow(run_status).to receive(:exception).and_return(RuntimeError.new("an error happened")) + end + + it "is not missing any required fields" do + missing_fields = required_fields.select do |key| + !Chef::DataCollector::Messages.run_end_message(reporter_data).key?(key) + end + expect(missing_fields).to eq([]) + end + + it "does not have any extra fields" do + extra_fields = Chef::DataCollector::Messages.run_end_message(reporter_data).keys.select do |key| + !required_fields.include?(key) && !optional_fields.include?(key) + end + expect(extra_fields).to eq([]) + end + end + end + + describe "#run_end_message in policy mode" do + let(:node) { Chef::Node.new } + let(:run_status) { Chef::RunStatus.new(node, Chef::EventDispatch::Dispatcher.new) } + let(:report1) { double("report1", report_data: { "status" => "updated" }) } + let(:report2) { double("report2", report_data: { "status" => "skipped" }) } + let(:reporter_data) do + { + run_status: run_status, + resources: [report1, report2], + } + end + + before do + allow(run_status).to receive(:start_time).and_return(Time.now) + allow(run_status).to receive(:end_time).and_return(Time.now) + node.policy_group = "test" + node.policy_name = "policy-test" + end + + it "includes a valid node object in the payload" do + message = Chef::DataCollector::Messages.run_end_message(reporter_data) + expect(message["node"]).to be_an_instance_of(Chef::Node) + end + + it "returns a sane JSON representation of the node object" do + node.chef_environment = "my_test_environment" + node.run_list.add("recipe[my_test_cookbook::default]") + message = FFI_Yajl::Parser.parse(Chef::DataCollector::Messages.run_end_message(reporter_data).to_json) + + expect(message["node"]["chef_environment"]).to eq("my_test_environment") + expect(message["node"]["run_list"]).to eq(["recipe[my_test_cookbook::default]"]) + expect(message["node"]["policy_name"]).to eq("policy-test") + expect(message["node"]["policy_group"]).to eq("test") + end + + context "when the run was successful" do + let(:required_fields) do + %w{ + chef_server_fqdn + entity_uuid + id + end_time + expanded_run_list + message_type + message_version + node + node_name + organization_name + resources + run_id + run_list + source + start_time + status + total_resource_count + updated_resource_count + deprecations + policy_name + policy_group + } + end let(:optional_fields) { %w{error} } before do @@ -166,6 +301,8 @@ describe Chef::DataCollector::Messages do total_resource_count updated_resource_count deprecations + policy_name + policy_group } end let(:optional_fields) { [] } diff --git a/spec/unit/formatters/error_description_spec.rb b/spec/unit/formatters/error_description_spec.rb index 8b088308fa..cf6372ed49 100644 --- a/spec/unit/formatters/error_description_spec.rb +++ b/spec/unit/formatters/error_description_spec.rb @@ -49,7 +49,21 @@ describe Chef::Formatters::ErrorDescription do describe "#display" do before do - stub_const("RUBY_PLATFORM", "ruby-foo-9000") + stub_const("Chef::VERSION", "1.2.3") + stub_const("RUBY_DESCRIPTION", "ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15]") + allow(subject).to receive(:caller) { Kernel.caller + ["/test/bin/chef-client:1:in `<main>'"] } + allow(File).to receive(:realpath).and_call_original + allow(File).to receive(:realpath).with("/test/bin/chef-client").and_return("/test/bin/chef-client") + end + + around do |ex| + old_program_name = $PROGRAM_NAME + begin + $PROGRAM_NAME = "chef-client" + ex.run + ensure + $PROGRAM_NAME = old_program_name + end end context "when no sections have been added" do @@ -60,9 +74,12 @@ describe Chef::Formatters::ErrorDescription do test title ================================================================================ -Platform: ---------- -ruby-foo-9000 +System Info: +------------ +chef_version=1.2.3 +ruby=ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15] +program_name=chef-client +executable=/test/bin/chef-client END end @@ -84,9 +101,37 @@ test heading ------------ test text -Platform: ---------- -ruby-foo-9000 +System Info: +------------ +chef_version=1.2.3 +ruby=ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15] +program_name=chef-client +executable=/test/bin/chef-client + + END + end + + end + + context "when node object is available" do + it "should output the expected sections" do + # This can't be in a before block because the spec-wide helper calls a + # reset on global values. + Chef.set_node({ "platform" => "openvms", "platform_version" => "8.4-2L1" }) + subject.display(out) + expect(out.out.string).to eq <<-END +================================================================================ +test title +================================================================================ + +System Info: +------------ +chef_version=1.2.3 +platform=openvms +platform_version=8.4-2L1 +ruby=ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15] +program_name=chef-client +executable=/test/bin/chef-client END end diff --git a/spec/unit/http/socketless_chef_zero_client_spec.rb b/spec/unit/http/socketless_chef_zero_client_spec.rb index 637e562799..4f3aed13c5 100644 --- a/spec/unit/http/socketless_chef_zero_client_spec.rb +++ b/spec/unit/http/socketless_chef_zero_client_spec.rb @@ -132,7 +132,7 @@ describe Chef::HTTP::SocketlessChefZeroClient do let(:method) { :GET } let(:relative_url) { "clients" } - let(:headers) { { "Accept" => "application/json" } } + let(:headers) { { "Accept" => "application/json", "X-Ops-Server-API-Version" => "2" } } let(:body) { false } let(:expected_rack_req) do @@ -144,6 +144,7 @@ describe Chef::HTTP::SocketlessChefZeroClient do "QUERY_STRING" => uri.query, "SERVER_PORT" => uri.port, "HTTP_HOST" => "localhost:#{uri.port}", + "HTTP_X_OPS_SERVER_API_VERSION" => "2", "rack.url_scheme" => "chefzero", "rack.input" => an_instance_of(StringIO), } diff --git a/spec/unit/knife/configure_client_spec.rb b/spec/unit/knife/configure_client_spec.rb index 3ecb89f827..0f83897564 100644 --- a/spec/unit/knife/configure_client_spec.rb +++ b/spec/unit/knife/configure_client_spec.rb @@ -58,8 +58,6 @@ describe Chef::Knife::ConfigureClient do it "should write out the config file" do allow(FileUtils).to receive(:mkdir_p) @knife.run - expect(@client_file.string).to match /log_level\s+\:info/ - expect(@client_file.string).to match /log_location\s+STDOUT/ expect(@client_file.string).to match /chef_server_url\s+'https\:\/\/chef\.example\.com'/ expect(@client_file.string).to match /validation_client_name\s+'chef-validator'/ end diff --git a/spec/unit/knife/cookbook_create_spec.rb b/spec/unit/knife/cookbook_create_spec.rb index f860a8bce8..ef86a0cd2b 100644 --- a/spec/unit/knife/cookbook_create_spec.rb +++ b/spec/unit/knife/cookbook_create_spec.rb @@ -34,228 +34,9 @@ describe Chef::Knife::CookbookCreate do # Fixes CHEF-2579 it "should expand the path of the cookbook directory" do - expect(File).to receive(:expand_path).with("~/tmp/monkeypants") - @knife.config = { :cookbook_path => "~/tmp/monkeypants" } - allow(@knife).to receive(:create_cookbook) - allow(@knife).to receive(:create_readme) - allow(@knife).to receive(:create_changelog) - allow(@knife).to receive(:create_metadata) + expect(Chef::Log).to receive(:fatal).with("knife cookbook create has been removed. Please use `chef generate cookbook` from the ChefDK") @knife.run end - it "should create a new cookbook with default values to copyright name, email, readme format and license if those are not supplied" do - @dir = Dir.tmpdir - @knife.config = { :cookbook_path => @dir } - expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "YOUR_COMPANY_NAME", "none") - expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "md") - expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) - expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "YOUR_COMPANY_NAME", "YOUR_EMAIL", "none", "md") - @knife.run - end - - it "should create a new cookbook with specified company name in the copyright section if one is specified" do - @dir = Dir.tmpdir - @knife.config = { - :cookbook_path => @dir, - :cookbook_copyright => "Chef Software, Inc.", - } - @knife.name_args = ["foobar"] - expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "none") - expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "md") - expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) - expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "YOUR_EMAIL", "none", "md") - @knife.run - end - - it "should create a new cookbook with specified copyright name and email if they are specified" do - @dir = Dir.tmpdir - @knife.config = { - :cookbook_path => @dir, - :cookbook_copyright => "Chef Software, Inc.", - :cookbook_email => "test@chef.io", - } - @knife.name_args = ["foobar"] - expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "none") - expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "md") - expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) - expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "test@chef.io", "none", "md") - @knife.run - end - - it "should create a new cookbook with specified copyright name and email and license information (true) if they are specified" do - @dir = Dir.tmpdir - @knife.config = { - :cookbook_path => @dir, - :cookbook_copyright => "Chef Software, Inc.", - :cookbook_email => "test@chef.io", - :cookbook_license => "apachev2", - } - @knife.name_args = ["foobar"] - expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "apachev2") - expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "md") - expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) - expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "test@chef.io", "apachev2", "md") - @knife.run - end - - it "should create a new cookbook with specified copyright name and email and license information (false) if they are specified" do - @dir = Dir.tmpdir - @knife.config = { - :cookbook_path => @dir, - :cookbook_copyright => "Chef Software, Inc.", - :cookbook_email => "test@chef.io", - :cookbook_license => false, - } - @knife.name_args = ["foobar"] - expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "none") - expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "md") - expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) - expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "test@chef.io", "none", "md") - @knife.run - end - - it "should create a new cookbook with specified copyright name and email and license information ('false' as string) if they are specified" do - @dir = Dir.tmpdir - @knife.config = { - :cookbook_path => @dir, - :cookbook_copyright => "Chef Software, Inc.", - :cookbook_email => "test@chef.io", - :cookbook_license => "false", - } - @knife.name_args = ["foobar"] - expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "none") - expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "md") - expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) - expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "test@chef.io", "none", "md") - @knife.run - end - - it "should allow specifying a gpl2 license" do - @dir = Dir.tmpdir - @knife.config = { - :cookbook_path => @dir, - :cookbook_copyright => "Chef Software, Inc.", - :cookbook_email => "test@chef.io", - :cookbook_license => "gplv2", - } - @knife.name_args = ["foobar"] - expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "gplv2") - expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "md") - expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) - expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "test@chef.io", "gplv2", "md") - @knife.run - end - - it "should allow specifying a gplv3 license" do - @dir = Dir.tmpdir - @knife.config = { - :cookbook_path => @dir, - :cookbook_copyright => "Chef Software, Inc.", - :cookbook_email => "test@chef.io", - :cookbook_license => "gplv3", - } - @knife.name_args = ["foobar"] - expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "gplv3") - expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "md") - expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) - expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "test@chef.io", "gplv3", "md") - @knife.run - end - - it "should allow specifying the mit license" do - @dir = Dir.tmpdir - @knife.config = { - :cookbook_path => @dir, - :cookbook_copyright => "Chef Software, Inc.", - :cookbook_email => "test@chef.io", - :cookbook_license => "mit", - } - @knife.name_args = ["foobar"] - expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "mit") - expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "md") - expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) - expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "test@chef.io", "mit", "md") - @knife.run - end - - it "should allow specifying the rdoc readme format" do - @dir = Dir.tmpdir - @knife.config = { - :cookbook_path => @dir, - :cookbook_copyright => "Chef Software, Inc.", - :cookbook_email => "test@chef.io", - :cookbook_license => "mit", - :readme_format => "rdoc", - } - @knife.name_args = ["foobar"] - expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "mit") - expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "rdoc") - expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) - expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "test@chef.io", "mit", "rdoc") - @knife.run - end - - it "should allow specifying the md readme format" do - @dir = Dir.tmpdir - @knife.config = { - :cookbook_path => @dir, - :cookbook_copyright => "Chef Software, Inc.", - :cookbook_email => "test@chef.io", - :cookbook_license => "mit", - :readme_format => "mkd", - } - @knife.name_args = ["foobar"] - expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "mit") - expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "mkd") - expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) - expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "test@chef.io", "mit", "mkd") - @knife.run - end - - it "should allow specifying the txt readme format" do - @dir = Dir.tmpdir - @knife.config = { - :cookbook_path => @dir, - :cookbook_copyright => "Chef Software, Inc.", - :cookbook_email => "test@chef.io", - :cookbook_license => "mit", - :readme_format => "txt", - } - @knife.name_args = ["foobar"] - expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "mit") - expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "txt") - expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) - expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "test@chef.io", "mit", "txt") - @knife.run - end - - it "should allow specifying an arbitrary readme format" do - @dir = Dir.tmpdir - @knife.config = { - :cookbook_path => @dir, - :cookbook_copyright => "Chef Software, Inc.", - :cookbook_email => "test@chef.io", - :cookbook_license => "mit", - :readme_format => "foo", - } - @knife.name_args = ["foobar"] - expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "mit") - expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "foo") - expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) - expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "test@chef.io", "mit", "foo") - @knife.run - end - - context "when the cookbooks path is set to nil" do - before do - Chef::Config[:cookbook_path] = nil - end - - it "should throw an argument error" do - @dir = Dir.tmpdir - expect { @knife.run }.to raise_error(ArgumentError) - end - end - end end diff --git a/spec/unit/knife/cookbook_download_spec.rb b/spec/unit/knife/cookbook_download_spec.rb index 38a4974774..1fb995f71d 100644 --- a/spec/unit/knife/cookbook_download_spec.rb +++ b/spec/unit/knife/cookbook_download_spec.rb @@ -47,44 +47,62 @@ describe Chef::Knife::CookbookDownload do @rest_mock = double("rest") allow(@knife).to receive(:rest).and_return(@rest_mock) - @manifest_data = { - :recipes => [ - { "path" => "recipes/foo.rb", - "url" => "http://example.org/files/foo.rb" }, - { "path" => "recipes/bar.rb", - "url" => "http://example.org/files/bar.rb" }, - ], - :templates => [ - { "path" => "templates/default/foo.erb", - "url" => "http://example.org/files/foo.erb" }, - { "path" => "templates/default/bar.erb", - "url" => "http://example.org/files/bar.erb" }, - ], - :attributes => [ - { "path" => "attributes/default.rb", - "url" => "http://example.org/files/default.rb" }, + expect(Chef::CookbookVersion).to receive(:load).with("foobar", "1.0.0"). + and_return(cookbook) + end + + let(:manifest_data) do + { + :all_files => [ + { + "path" => "recipes/foo.rb", + "name" => "recipes/foo.rb", + "url" => "http://example.org/files/foo.rb", + }, + { + "path" => "recipes/bar.rb", + "name" => "recipes/bar.rb", + "url" => "http://example.org/files/bar.rb", + }, + { + "path" => "templates/default/foo.erb", + "name" => "templates/foo.erb", + "url" => "http://example.org/files/foo.erb", + }, + { + "path" => "templates/default/bar.erb", + "name" => "templates/bar.erb", + "url" => "http://example.org/files/bar.erb", + }, + { + "path" => "attributes/default.rb", + "name" => "attributes/default.rb", + "url" => "http://example.org/files/default.rb", + }, ], } + end - @cookbook_mock = double("cookbook") - allow(@cookbook_mock).to receive(:version).and_return("1.0.0") - allow(@cookbook_mock).to receive(:manifest).and_return(@manifest_data) - expect(Chef::CookbookVersion).to receive(:load).with("foobar", "1.0.0"). - and_return(@cookbook_mock) + let (:cookbook) do + cb = Chef::CookbookVersion.new("foobar") + cb.version = "1.0.0" + cb.manifest = manifest_data + cb end - it "should determine which version if one was not explicitly specified" do - allow(@cookbook_mock).to receive(:manifest).and_return({}) - expect(@knife).to receive(:determine_version).and_return("1.0.0") - expect(File).to receive(:exists?).with("/var/tmp/chef/foobar-1.0.0").and_return(false) - allow(Chef::CookbookVersion).to receive(:COOKBOOK_SEGEMENTS).and_return([]) - @knife.run + describe "and no version" do + let (:manifest_data) { { all_files: [] } } + it "should determine which version to download" do + expect(@knife).to receive(:determine_version).and_return("1.0.0") + expect(File).to receive(:exists?).with("/var/tmp/chef/foobar-1.0.0").and_return(false) + @knife.run + end end describe "and a version" do before(:each) do @knife.name_args << "1.0.0" - @files = @manifest_data.values.map { |v| v.map { |i| i["path"] } }.flatten.uniq + @files = manifest_data.values.map { |v| v.map { |i| i["path"] } }.flatten.uniq @files_mocks = {} @files.map { |f| File.basename(f) }.flatten.uniq.each do |f| @files_mocks[f] = double("#{f}_mock") diff --git a/spec/unit/knife/cookbook_show_spec.rb b/spec/unit/knife/cookbook_show_spec.rb index 749e50c647..1e8ea836d7 100644 --- a/spec/unit/knife/cookbook_show_spec.rb +++ b/spec/unit/knife/cookbook_show_spec.rb @@ -47,9 +47,9 @@ describe Chef::Knife::CookbookShow do let (:manifest) do { - "recipes" => [ + "all_files" => [ { - :name => "default.rb", + :name => "recipes/default.rb", :path => "recipes/default.rb", :checksum => "1234", :url => "http://example.org/files/default.rb", @@ -101,9 +101,42 @@ describe Chef::Knife::CookbookShow do knife.name_args << "0.1.0" end + let(:output) do + { "cookbook_name" => "cookbook_name", + "name" => "cookbook_name-0.0.0", + "frozen?" => false, + "version" => "0.0.0", + "metadata" => { + "name" => nil, + "description" => "", + "long_description" => "", + "maintainer" => nil, + "maintainer_email" => nil, + "license" => "All rights reserved", + "platforms" => {}, + "dependencies" => {}, + "providing" => {}, + "attributes" => {}, + "recipes" => {}, + "version" => "0.0.0", + "source_url" => "", + "issues_url" => "", + "privacy" => false, + "chef_versions" => [], + "ohai_versions" => [], + "gems" => [], + }, + "recipes" => + [{ "name" => "recipes/default.rb", + "path" => "recipes/default.rb", + "checksum" => "1234", + "url" => "http://example.org/files/default.rb" }], + } + end + it "should show the specific part of a cookbook" do expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb) - expect(knife).to receive(:output).with(cb) + expect(knife).to receive(:output).with(output) knife.run end end @@ -115,7 +148,7 @@ describe Chef::Knife::CookbookShow do it "should print the json of the part" do expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb) - expect(knife).to receive(:output).with(cb.manifest["recipes"]) + expect(knife).to receive(:output).with(cb.files_for("recipes")) knife.run end end @@ -137,30 +170,30 @@ describe Chef::Knife::CookbookShow do before(:each) do knife.name_args = [ "cookbook_name", "0.1.0", "files", "afile.rb" ] cb.manifest = { - "files" => [ + "all_files" => [ { - :name => "afile.rb", + :name => "files/afile.rb", :path => "files/host-examplehost.example.org/afile.rb", :checksum => "1111", :specificity => "host-examplehost.example.org", :url => "http://example.org/files/1111", }, { - :name => "afile.rb", + :name => "files/afile.rb", :path => "files/ubuntu-9.10/afile.rb", :checksum => "2222", :specificity => "ubuntu-9.10", :url => "http://example.org/files/2222", }, { - :name => "afile.rb", + :name => "files/afile.rb", :path => "files/ubuntu/afile.rb", :checksum => "3333", :specificity => "ubuntu", :url => "http://example.org/files/3333", }, { - :name => "afile.rb", + :name => "files/afile.rb", :path => "files/default/afile.rb", :checksum => "4444", :specificity => "default", diff --git a/spec/unit/knife/core/cookbook_scm_repo_spec.rb b/spec/unit/knife/core/cookbook_scm_repo_spec.rb index 137bdddafb..3c16f93533 100644 --- a/spec/unit/knife/core/cookbook_scm_repo_spec.rb +++ b/spec/unit/knife/core/cookbook_scm_repo_spec.rb @@ -83,7 +83,7 @@ BRANCHES it "exits when the git repo is dirty" do @dirty_status = Mixlib::ShellOut.new @dirty_status.stdout.replace(<<-DIRTY) - M chef/lib/chef/knife/cookbook_site_vendor.rb + M chef/lib/chef/knife/cookbook_site_install.rb DIRTY expect(@cookbook_repo).to receive(:shell_out!).with("git status --porcelain", :cwd => @repo_path).and_return(@dirty_status) expect { @cookbook_repo.sanity_check }.to raise_error(SystemExit) diff --git a/spec/unit/knife/core/custom_manifest_loader_spec.rb b/spec/unit/knife/core/custom_manifest_loader_spec.rb deleted file mode 100644 index 814ac8a027..0000000000 --- a/spec/unit/knife/core/custom_manifest_loader_spec.rb +++ /dev/null @@ -1,41 +0,0 @@ -# -# Copyright:: Copyright 2015-2016, Chef Software, Inc -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require "spec_helper" - -describe Chef::Knife::SubcommandLoader::CustomManifestLoader do - let(:ec2_server_create_plugin) { "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_server_create.rb" } - let(:manifest_content) do - { "plugins" => { - "knife-ec2" => { - "paths" => [ - ec2_server_create_plugin, - ], - }, - }, - } - end - let(:loader) do - Chef::Knife::SubcommandLoader::CustomManifestLoader.new(File.join(CHEF_SPEC_DATA, "knife-site-subcommands"), - manifest_content) - end - - it "uses paths from the manifest instead of searching gems" do - expect(Gem::Specification).not_to receive(:latest_specs).and_call_original - expect(loader.subcommand_files).to include(ec2_server_create_plugin) - end -end diff --git a/spec/unit/knife/core/subcommand_loader_spec.rb b/spec/unit/knife/core/subcommand_loader_spec.rb index b235102a0b..5db0bb73e5 100644 --- a/spec/unit/knife/core/subcommand_loader_spec.rb +++ b/spec/unit/knife/core/subcommand_loader_spec.rb @@ -43,12 +43,6 @@ describe Chef::Knife::SubcommandLoader do allow(File).to receive(:read).with(File.join(home, ".chef", "plugin_manifest.json")).and_return("{ \"_autogenerated_command_paths\": {}}") expect(Chef::Knife::SubcommandLoader.for_config(config_dir)).to be_a Chef::Knife::SubcommandLoader::HashedCommandLoader end - - it "creates a CustomManifestLoader with then manifest has a key other than _autogenerated_command_paths" do - Chef::Config[:treat_deprecation_warnings_as_errors] = false - allow(File).to receive(:read).with(File.join(home, ".chef", "plugin_manifest.json")).and_return("{ \"plugins\": {}}") - expect(Chef::Knife::SubcommandLoader.for_config(config_dir)).to be_a Chef::Knife::SubcommandLoader::CustomManifestLoader - end end context "when ~/.chef/plugin_manifest.json does not exist" do diff --git a/spec/unit/knife/core/ui_spec.rb b/spec/unit/knife/core/ui_spec.rb index 38614f44d9..0b986258b7 100644 --- a/spec/unit/knife/core/ui_spec.rb +++ b/spec/unit/knife/core/ui_spec.rb @@ -3,7 +3,7 @@ # Author:: Tim Hinderliter (<tim@chef.io>) # Author:: Daniel DeLeo (<dan@chef.io>) # Author:: John Keiser (<jkeiser@chef.io>) -# Copyright:: Copyright 2008-2016, Chef Software Inc. +# Copyright:: Copyright 2008-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,7 +31,6 @@ describe Chef::Knife::UI do :field_separator => ".", } @ui = Chef::Knife::UI.new(@out, @err, @in, @config) - Chef::Config[:treat_deprecation_warnings_as_errors] = false end class TestObject < OpenStruct diff --git a/spec/unit/knife_spec.rb b/spec/unit/knife_spec.rb index 9569526b2a..00c629b680 100644 --- a/spec/unit/knife_spec.rb +++ b/spec/unit/knife_spec.rb @@ -144,9 +144,9 @@ describe Chef::Knife do end it "finds a subcommand class based on ARGV" do - Chef::Knife.subcommands["cookbook_site_vendor"] = :CookbookSiteVendor + Chef::Knife.subcommands["cookbook_site_install"] = :CookbookSiteInstall Chef::Knife.subcommands["cookbook"] = :Cookbook - expect(Chef::Knife.subcommand_class_from(%w{cookbook site vendor --help foo bar baz})).to eq(:CookbookSiteVendor) + expect(Chef::Knife.subcommand_class_from(%w{cookbook site install --help foo bar baz})).to eq(:CookbookSiteInstall) end it "special case sets the subcommand_loader to GemGlobLoader when running rehash" do diff --git a/spec/unit/lwrp_spec.rb b/spec/unit/lwrp_spec.rb index 28773a3c30..75ebbb8f02 100644 --- a/spec/unit/lwrp_spec.rb +++ b/spec/unit/lwrp_spec.rb @@ -417,9 +417,8 @@ describe "LWRP" do Chef::Config[:treat_deprecation_warnings_as_errors] = @old_treat_deprecation_warnings_as_errors end - it "should load the provider into a properly-named class" do - expect(Chef::Provider.const_get("LwrpBuckPasser")).to be_kind_of(Class) - expect(Chef::Provider::LwrpBuckPasser <= Chef::Provider::LWRPBase).to be_truthy + it "should not load the provider into a const" do + expect(defined?(Chef::Provider::LwrpBuckPasser)).to be_nil end it "should create a method for each action" do @@ -571,55 +570,28 @@ describe "LWRP" do end context "resource class created" do + let(:test_lwrp_class) { @test_lwrp_class } before(:context) do @tmpdir = Dir.mktmpdir("lwrp_test") resource_path = File.join(@tmpdir, "once.rb") IO.write(resource_path, "default_action :create") - - @old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors] - Chef::Config[:treat_deprecation_warnings_as_errors] = false - Chef::Resource::LWRPBase.build_from_file("lwrp", resource_path, nil) + @test_lwrp_class = Chef::Resource::LWRPBase.build_from_file("lwrp", resource_path, nil) end after(:context) do FileUtils.remove_entry @tmpdir - Chef::Config[:treat_deprecation_warnings_as_errors] = @old_treat_deprecation_warnings_as_errors end - it "should load the resource into a properly-named class" do - expect(Chef::Resource::LwrpOnce).to be_kind_of(Class) - expect(Chef::Resource::LwrpOnce <= Chef::Resource::LWRPBase).to be_truthy + it "should not load the resource into a const" do + expect(defined?(Chef::Resource::LwrpOnce)).to be_nil end - it "get_lwrp(:lwrp_once).new is a Chef::Resource::LwrpOnce" do + it "get_lwrp(:lwrp_once).new is an instance of the LWRP class" do lwrp = get_lwrp(:lwrp_once).new("hi") - expect(lwrp.kind_of?(Chef::Resource::LwrpOnce)).to be_truthy - expect(lwrp.is_a?(Chef::Resource::LwrpOnce)).to be_truthy - expect(get_lwrp(:lwrp_once) === lwrp).to be_truthy - expect(Chef::Resource::LwrpOnce === lwrp).to be_truthy - end - - it "Chef::Resource::LwrpOnce.new is a get_lwrp(:lwrp_once)" do - lwrp = Chef::Resource::LwrpOnce.new("hi") - expect(lwrp.kind_of?(get_lwrp(:lwrp_once))).to be_truthy - expect(lwrp.is_a?(get_lwrp(:lwrp_once))).to be_truthy + expect(lwrp.kind_of?(test_lwrp_class)).to be_truthy + expect(lwrp.is_a?(test_lwrp_class)).to be_truthy expect(get_lwrp(:lwrp_once) === lwrp).to be_truthy - expect(Chef::Resource::LwrpOnce === lwrp).to be_truthy - end - - it "works even if LwrpOnce exists in the top level" do - module ::LwrpOnce - end - expect(Chef::Resource::LwrpOnce).not_to eq(::LwrpOnce) - end - - it "allows monkey patching of the lwrp through Chef::Resource" do - monkey = Module.new do - def issue_3607 - end - end - Chef::Resource::LwrpOnce.send(:include, monkey) - expect { get_lwrp(:lwrp_once).new("blah").issue_3607 }.not_to raise_error + expect(test_lwrp_class === lwrp).to be_truthy end context "with a subclass of get_lwrp(:lwrp_once)" do @@ -634,54 +606,12 @@ describe "LWRP" do expect(subclass === lwrp).to be_truthy expect(lwrp.class === subclass) end - it "subclass.new is a Chef::Resource::LwrpOnce" do - lwrp = subclass.new("hi") - expect(lwrp.kind_of?(Chef::Resource::LwrpOnce)).to be_truthy - expect(lwrp.is_a?(Chef::Resource::LwrpOnce)).to be_truthy - expect(Chef::Resource::LwrpOnce === lwrp).to be_truthy - expect(lwrp.class === Chef::Resource::LwrpOnce) - end - it "subclass.new is a get_lwrp(:lwrp_once)" do - lwrp = subclass.new("hi") - expect(lwrp.kind_of?(get_lwrp(:lwrp_once))).to be_truthy - expect(lwrp.is_a?(get_lwrp(:lwrp_once))).to be_truthy - expect(get_lwrp(:lwrp_once) === lwrp).to be_truthy - expect(lwrp.class === get_lwrp(:lwrp_once)) - end - it "Chef::Resource::LwrpOnce.new is *not* a subclass" do - lwrp = Chef::Resource::LwrpOnce.new("hi") - expect(lwrp.kind_of?(subclass)).to be_falsey - expect(lwrp.is_a?(subclass)).to be_falsey - expect(subclass === lwrp.class).to be_falsey - expect(subclass === Chef::Resource::LwrpOnce).to be_falsey - end - it "get_lwrp(:lwrp_once).new is *not* a subclass" do - lwrp = get_lwrp(:lwrp_once).new("hi") - expect(lwrp.kind_of?(subclass)).to be_falsey - expect(lwrp.is_a?(subclass)).to be_falsey - expect(subclass === lwrp.class).to be_falsey - expect(subclass === get_lwrp(:lwrp_once)).to be_falsey - end - end - - context "with a subclass of Chef::Resource::LwrpOnce" do - let(:subclass) do - Class.new(Chef::Resource::LwrpOnce) - end - - it "subclass.new is a subclass" do - lwrp = subclass.new("hi") - expect(lwrp.kind_of?(subclass)).to be_truthy - expect(lwrp.is_a?(subclass)).to be_truthy - expect(subclass === lwrp).to be_truthy - expect(lwrp.class === subclass) - end - it "subclass.new is a Chef::Resource::LwrpOnce" do + it "subclass.new is an instance of the LWRP class" do lwrp = subclass.new("hi") - expect(lwrp.kind_of?(Chef::Resource::LwrpOnce)).to be_truthy - expect(lwrp.is_a?(Chef::Resource::LwrpOnce)).to be_truthy - expect(Chef::Resource::LwrpOnce === lwrp).to be_truthy - expect(lwrp.class === Chef::Resource::LwrpOnce) + expect(lwrp.kind_of?(test_lwrp_class)).to be_truthy + expect(lwrp.is_a?(test_lwrp_class)).to be_truthy + expect(test_lwrp_class === lwrp).to be_truthy + expect(lwrp.class === test_lwrp_class) end it "subclass.new is a get_lwrp(:lwrp_once)" do lwrp = subclass.new("hi") @@ -690,13 +620,6 @@ describe "LWRP" do expect(get_lwrp(:lwrp_once) === lwrp).to be_truthy expect(lwrp.class === get_lwrp(:lwrp_once)) end - it "Chef::Resource::LwrpOnce.new is *not* a subclass" do - lwrp = Chef::Resource::LwrpOnce.new("hi") - expect(lwrp.kind_of?(subclass)).to be_falsey - expect(lwrp.is_a?(subclass)).to be_falsey - expect(subclass === lwrp.class).to be_falsey - expect(subclass === Chef::Resource::LwrpOnce).to be_falsey - end it "get_lwrp(:lwrp_once).new is *not* a subclass" do lwrp = get_lwrp(:lwrp_once).new("hi") expect(lwrp.kind_of?(subclass)).to be_falsey @@ -721,8 +644,6 @@ describe "LWRP" do end class MyAwesomeProvider < Chef::Provider::LWRPBase - use_inline_resources - provides :my_awesome_resource action :create do diff --git a/spec/unit/mixin/command_spec.rb b/spec/unit/mixin/command_spec.rb deleted file mode 100644 index e9f0dacad6..0000000000 --- a/spec/unit/mixin/command_spec.rb +++ /dev/null @@ -1,107 +0,0 @@ -# -# Author:: Hongli Lai (hongli@phusion.nl) -# Copyright:: Copyright 2009-2016, Phusion -# 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::Mixin::Command, :volatile do - - if windows? - - skip("TODO MOVE: this is a platform specific integration test.") - - else - - describe "popen4" do - include Chef::Mixin::Command - - it "should be possible to read the child process's stdout and stderr" do - popen4("sh -c 'echo hello && echo world >&2'") do |pid, stdin, stdout, stderr| - expect(stdout.read).to eq("hello\n") - expect(stderr.read).to eq("world\n") - end - end - - it "should default all commands to be run in the POSIX standard C locale" do - popen4("echo $LC_ALL") do |pid, stdin, stdout, stderr| - expect(stdout.read.strip).to eq("C") - end - end - - it "should respect locale when specified explicitly" do - popen4("echo $LC_ALL", :environment => { "LC_ALL" => "es" }) do |pid, stdin, stdout, stderr| - expect(stdout.read.strip).to eq("es") - end - end - - it "should end when the child process reads from STDIN and a block is given" do - expect do - Timeout.timeout(10) do - popen4("ruby -e 'while gets; end'", :waitlast => true) do |pid, stdin, stdout, stderr| - (1..5).each { |i| stdin.puts "#{i}" } - end - end - end.not_to raise_error - end - - describe "when a process detaches but doesn't close STDOUT and STDERR [CHEF-584]" do - - it "returns immediately after the first child process exits" do - expect do - Timeout.timeout(10) do - evil_forker = "exit if fork; 10.times { sleep 1}" - popen4("ruby -e '#{evil_forker}'") do |pid, stdin, stdout, stderr| - end - end end.not_to raise_error - end - - end - - end - - describe "run_command" do - include Chef::Mixin::Command - - it "logs the command's stderr and stdout output if the command failed" do - allow(Chef::Log).to receive(:level).and_return(:debug) - begin - run_command(:command => "sh -c 'echo hello; echo world >&2; false'") - violated "Exception expected, but nothing raised." - rescue => e - expect(e.message).to match(/STDOUT: hello/) - expect(e.message).to match(/STDERR: world/) - end - end - - describe "when a process detaches but doesn't close STDOUT and STDERR [CHEF-584]" do - it "returns successfully" do - # CHEF-2916 might have added a slight delay here, or our CI - # infrastructure is burdened. Bumping timeout from 2 => 4 -- - # btm - # Serdar - During Solaris tests, we've seen that processes - # are taking a long time to exit. Bumping timeout now to 10. - expect do - Timeout.timeout(10) do - evil_forker = "exit if fork; 10.times { sleep 1}" - run_command(:command => "ruby -e '#{evil_forker}'") - end end.not_to raise_error - end - - end - end - end -end diff --git a/spec/unit/mixin/path_sanity_spec.rb b/spec/unit/mixin/path_sanity_spec.rb index 675b5722be..c9ed4e2595 100644 --- a/spec/unit/mixin/path_sanity_spec.rb +++ b/spec/unit/mixin/path_sanity_spec.rb @@ -1,6 +1,6 @@ # # Author:: Seth Chisamore (<schisamo@chef.io>) -# Copyright:: Copyright 2011-2016, Chef Software Inc. +# Copyright:: Copyright 2011-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -41,19 +41,19 @@ describe Chef::Mixin::PathSanity do it "adds all useful PATHs even if environment is an empty hash" do env = {} @sanity.enforce_path_sanity(env) - expect(env["PATH"]).to eq("#{@ruby_bindir}:#{@gem_bindir}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") + expect(env["PATH"]).to eq("#{@gem_bindir}:#{@ruby_bindir}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") end it "adds all useful PATHs that are not yet in PATH to PATH" do env = { "PATH" => "" } @sanity.enforce_path_sanity(env) - expect(env["PATH"]).to eq("#{@ruby_bindir}:#{@gem_bindir}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") + expect(env["PATH"]).to eq("#{@gem_bindir}:#{@ruby_bindir}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") end it "does not re-add paths that already exist in PATH" do env = { "PATH" => "/usr/bin:/sbin:/bin" } @sanity.enforce_path_sanity(env) - expect(env["PATH"]).to eq("/usr/bin:/sbin:/bin:#{@ruby_bindir}:#{@gem_bindir}:/usr/local/sbin:/usr/local/bin:/usr/sbin") + expect(env["PATH"]).to eq("#{@gem_bindir}:#{@ruby_bindir}:/usr/bin:/sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin") end it "creates path with utf-8 encoding" do @@ -65,7 +65,7 @@ describe Chef::Mixin::PathSanity do it "adds the current executing Ruby's bindir and Gem bindir to the PATH" do env = { "PATH" => "" } @sanity.enforce_path_sanity(env) - expect(env["PATH"]).to eq("#{@ruby_bindir}:#{@gem_bindir}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") + expect(env["PATH"]).to eq("#{@gem_bindir}:#{@ruby_bindir}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") end it "does not create entries for Ruby/Gem bindirs if they exist in SANE_PATH or PATH" do @@ -75,7 +75,7 @@ describe Chef::Mixin::PathSanity do allow(RbConfig::CONFIG).to receive(:[]).with("bindir").and_return(ruby_bindir) env = { "PATH" => gem_bindir } @sanity.enforce_path_sanity(env) - expect(env["PATH"]).to eq("/yo/gabba/gabba:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") + expect(env["PATH"]).to eq("/usr/bin:/yo/gabba/gabba:/usr/local/sbin:/usr/local/bin:/usr/sbin:/sbin:/bin") end it "builds a valid windows path" do @@ -86,7 +86,7 @@ describe Chef::Mixin::PathSanity do allow(ChefConfig).to receive(:windows?).and_return(true) env = { "PATH" => 'C:\Windows\system32;C:\mr\softie' } @sanity.enforce_path_sanity(env) - expect(env["PATH"]).to eq("C:\\Windows\\system32;C:\\mr\\softie;#{ruby_bindir};#{gem_bindir}") + expect(env["PATH"]).to eq("#{gem_bindir};#{ruby_bindir};C:\\Windows\\system32;C:\\mr\\softie") end end end diff --git a/spec/unit/mixin/shell_out_spec.rb b/spec/unit/mixin/shell_out_spec.rb index aa38639c1c..9b12969026 100644 --- a/spec/unit/mixin/shell_out_spec.rb +++ b/spec/unit/mixin/shell_out_spec.rb @@ -21,91 +21,19 @@ # require "spec_helper" +require "chef/mixin/path_sanity" describe Chef::Mixin::ShellOut do + include Chef::Mixin::PathSanity + let(:shell_out_class) { Class.new { include Chef::Mixin::ShellOut } } subject(:shell_out_obj) { shell_out_class.new } - describe "#run_command_compatible_options" do - subject { shell_out_obj.run_command_compatible_options(command_args) } - let(:command_args) { [ cmd, options ] } - let(:cmd) { "echo '#{rand(1000)}'" } - - let(:output) { StringIO.new } - let!(:capture_log_output) { Chef::Log.logger = Logger.new(output) } - let(:assume_deprecation_log_level) { allow(Chef::Log).to receive(:level).and_return(:warn) } - - before do - Chef::Config[:treat_deprecation_warnings_as_errors] = false - end - - context "without options" do - let(:command_args) { [ cmd ] } - - it "should not edit command args" do - is_expected.to eql(command_args) - end - end - - context "without deprecated options" do - let(:options) { { :environment => environment } } - let(:environment) { { "LC_ALL" => "C", "LANG" => "C", "LANGUAGE" => "C" } } - - it "should not edit command args" do - is_expected.to eql(command_args) - end - end - - def self.should_emit_deprecation_warning_about(old_option, new_option) - it "should emit a deprecation warning" do - assume_deprecation_log_level && capture_log_output - subject - expect(output.string).to match Regexp.escape(old_option.to_s) - expect(output.string).to match Regexp.escape(new_option.to_s) - end - end - - context "with :command_log_level option" do - let(:options) { { :command_log_level => command_log_level } } - let(:command_log_level) { :warn } - - it "should convert :command_log_level to :log_level" do - is_expected.to eql [ cmd, { :log_level => command_log_level } ] - end - - should_emit_deprecation_warning_about :command_log_level, :log_level - end - - context "with :command_log_prepend option" do - let(:options) { { :command_log_prepend => command_log_prepend } } - let(:command_log_prepend) { "PROVIDER:" } - - it "should convert :command_log_prepend to :log_tag" do - is_expected.to eql [ cmd, { :log_tag => command_log_prepend } ] - end - - should_emit_deprecation_warning_about :command_log_prepend, :log_tag - end - - context "with 'command_log_level' option" do - let(:options) { { "command_log_level" => command_log_level } } - let(:command_log_level) { :warn } - - it "should convert 'command_log_level' to :log_level" do - is_expected.to eql [ cmd, { :log_level => command_log_level } ] - end - - should_emit_deprecation_warning_about :command_log_level, :log_level - end - - context "with 'command_log_prepend' option" do - let(:options) { { "command_log_prepend" => command_log_prepend } } - let(:command_log_prepend) { "PROVIDER:" } - - it "should convert 'command_log_prepend' to :log_tag" do - is_expected.to eql [ cmd, { :log_tag => command_log_prepend } ] - end - should_emit_deprecation_warning_about :command_log_prepend, :log_tag + def env_path + if Chef::Platform.windows? + "Path" + else + "PATH" end end @@ -127,13 +55,13 @@ describe Chef::Mixin::ShellOut do describe "when the last argument is a Hash" do describe "and environment is an option" do it "should not change environment language settings when they are set to nil" do - options = { :environment => { "LC_ALL" => nil, "LANGUAGE" => nil, "LANG" => nil } } + options = { :environment => { "LC_ALL" => nil, "LANGUAGE" => nil, "LANG" => nil, env_path => nil } } expect(shell_out_obj).to receive(:shell_out_command).with(cmd, options).and_return(true) shell_out_obj.shell_out(cmd, options) end it "should not change environment language settings when they are set to non-nil" do - options = { :environment => { "LC_ALL" => "en_US.UTF-8", "LANGUAGE" => "en_US.UTF-8", "LANG" => "en_US.UTF-8" } } + options = { :environment => { "LC_ALL" => "en_US.UTF-8", "LANGUAGE" => "en_US.UTF-8", "LANG" => "en_US.UTF-8", env_path => "foo:bar:baz" } } expect(shell_out_obj).to receive(:shell_out_command).with(cmd, options).and_return(true) shell_out_obj.shell_out(cmd, options) end @@ -146,6 +74,7 @@ describe Chef::Mixin::ShellOut do "LC_ALL" => Chef::Config[:internal_locale], "LANG" => Chef::Config[:internal_locale], "LANGUAGE" => Chef::Config[:internal_locale], + env_path => sanitized_path, }, }).and_return(true) shell_out_obj.shell_out(cmd, options) @@ -159,6 +88,7 @@ describe Chef::Mixin::ShellOut do "LC_ALL" => Chef::Config[:internal_locale], "LANG" => Chef::Config[:internal_locale], "LANGUAGE" => Chef::Config[:internal_locale], + env_path => sanitized_path, }, }).and_return(true) shell_out_obj.shell_out(cmd, options) @@ -168,13 +98,13 @@ describe Chef::Mixin::ShellOut do describe "and env is an option" do it "should not change env when langauge options are set to nil" do - options = { :env => { "LC_ALL" => nil, "LANG" => nil, "LANGUAGE" => nil } } + options = { :env => { "LC_ALL" => nil, "LANG" => nil, "LANGUAGE" => nil, env_path => nil } } expect(shell_out_obj).to receive(:shell_out_command).with(cmd, options).and_return(true) shell_out_obj.shell_out(cmd, options) end it "should not change env when language options are set to non-nil" do - options = { :env => { "LC_ALL" => "de_DE.UTF-8", "LANG" => "de_DE.UTF-8", "LANGUAGE" => "de_DE.UTF-8" } } + options = { :env => { "LC_ALL" => "de_DE.UTF-8", "LANG" => "de_DE.UTF-8", "LANGUAGE" => "de_DE.UTF-8", env_path => "foo:bar:baz" } } expect(shell_out_obj).to receive(:shell_out_command).with(cmd, options).and_return(true) shell_out_obj.shell_out(cmd, options) end @@ -187,6 +117,7 @@ describe Chef::Mixin::ShellOut do "LC_ALL" => Chef::Config[:internal_locale], "LANG" => Chef::Config[:internal_locale], "LANGUAGE" => Chef::Config[:internal_locale], + env_path => sanitized_path, }, }).and_return(true) shell_out_obj.shell_out(cmd, options) @@ -200,6 +131,7 @@ describe Chef::Mixin::ShellOut do "LC_ALL" => Chef::Config[:internal_locale], "LANG" => Chef::Config[:internal_locale], "LANGUAGE" => Chef::Config[:internal_locale], + env_path => sanitized_path, }, }).and_return(true) shell_out_obj.shell_out(cmd, options) @@ -216,6 +148,7 @@ describe Chef::Mixin::ShellOut do "LC_ALL" => Chef::Config[:internal_locale], "LANG" => Chef::Config[:internal_locale], "LANGUAGE" => Chef::Config[:internal_locale], + env_path => sanitized_path, }, }).and_return(true) shell_out_obj.shell_out(cmd, options) @@ -230,6 +163,7 @@ describe Chef::Mixin::ShellOut do "LC_ALL" => Chef::Config[:internal_locale], "LANG" => Chef::Config[:internal_locale], "LANGUAGE" => Chef::Config[:internal_locale], + env_path => sanitized_path, }, }).and_return(true) shell_out_obj.shell_out(cmd) diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb index 40780e523b..73f0e6da09 100644 --- a/spec/unit/node_spec.rb +++ b/spec/unit/node_spec.rb @@ -265,12 +265,6 @@ describe Chef::Node do expect(node[:snoopy][:is_a_puppy]).to eq(true) end - it "should allow you to set an attribute with set_unless with method_missing but emit a deprecation warning" do - Chef::Config[:treat_deprecation_warnings_as_errors] = false - node.normal_unless.snoopy.is_a_puppy = false - expect(node[:snoopy][:is_a_puppy]).to eq(false) - end - it "should allow you to set an attribute with set_unless" do node.normal_unless[:snoopy][:is_a_puppy] = false expect(node[:snoopy][:is_a_puppy]).to eq(false) diff --git a/spec/unit/policy_builder/policyfile_spec.rb b/spec/unit/policy_builder/policyfile_spec.rb index 307bd45c18..c9086c2f63 100644 --- a/spec/unit/policy_builder/policyfile_spec.rb +++ b/spec/unit/policy_builder/policyfile_spec.rb @@ -100,8 +100,8 @@ describe Chef::PolicyBuilder::Policyfile do http = double("Chef::ServerAPI") server_url = "https://api.opscode.com/organizations/example" Chef::Config[:chef_server_url] = server_url - expect(Chef::ServerAPI).to receive(:new).with(server_url).and_return(http) - expect(policy_builder.http_api).to eq(http) + expect(Chef::ServerAPI).to receive(:new).with(server_url, version_class: Chef::CookbookManifestVersions).and_return(http) + expect(policy_builder.api_service).to eq(http) end describe "reporting unsupported features" do @@ -150,7 +150,7 @@ describe Chef::PolicyBuilder::Policyfile do describe "loading policy data" do - let(:http_api) { double("Chef::ServerAPI") } + let(:api_service) { double("Chef::ServerAPI") } let(:configured_environment) { nil } @@ -172,7 +172,7 @@ describe Chef::PolicyBuilder::Policyfile do before do Chef::Config[:policy_document_native_api] = false Chef::Config[:deployment_group] = "example-policy-stage" - allow(policy_builder).to receive(:http_api).and_return(http_api) + allow(policy_builder).to receive(:api_service).and_return(api_service) end describe "when using compatibility mode (policy_document_native_api == false)" do @@ -185,7 +185,7 @@ describe Chef::PolicyBuilder::Policyfile do let(:error404) { Net::HTTPServerException.new("404 message", :body) } before do - expect(http_api).to receive(:get). + expect(api_service).to receive(:get). with("data/policyfiles/example-policy-stage"). and_raise(error404) end @@ -212,7 +212,7 @@ describe Chef::PolicyBuilder::Policyfile do let(:policy_relative_url) { "data/policyfiles/example-policy-stage" } before do - expect(http_api).to receive(:get).with(policy_relative_url).and_return(parsed_policyfile_json) + expect(api_service).to receive(:get).with(policy_relative_url).and_return(parsed_policyfile_json) end it "fetches the policy file from a data bag item" do @@ -253,7 +253,7 @@ describe Chef::PolicyBuilder::Policyfile do let(:policy_relative_url) { "policy_groups/policy-stage/policies/example" } before do - expect(http_api).to receive(:get).with(policy_relative_url).and_return(parsed_policyfile_json) + expect(api_service).to receive(:get).with(policy_relative_url).and_return(parsed_policyfile_json) end it "fetches the policy file from a data bag item" do @@ -617,7 +617,7 @@ describe Chef::PolicyBuilder::Policyfile do policy_builder.finish_load_node(node) policy_builder.build_node - expect(http_api).to receive(:get).with(cookbook1_url). + expect(api_service).to receive(:get).with(cookbook1_url). and_raise(error404) end @@ -687,9 +687,9 @@ describe Chef::PolicyBuilder::Policyfile do context "when the cookbooks exist on the server" do before do - expect(http_api).to receive(:get).with(cookbook1_url). + expect(api_service).to receive(:get).with(cookbook1_url). and_return(example1_cookbook_data) - expect(http_api).to receive(:get).with(cookbook2_url). + expect(api_service).to receive(:get).with(cookbook2_url). and_return(example2_cookbook_data) expect(Chef::CookbookVersion).to receive(:from_cb_artifact_data).with(example1_cookbook_data). @@ -720,9 +720,9 @@ describe Chef::PolicyBuilder::Policyfile do context "when the cookbooks exist on the server" do before do - expect(http_api).to receive(:get).with(cookbook1_url). + expect(api_service).to receive(:get).with(cookbook1_url). and_return(example1_cookbook_data) - expect(http_api).to receive(:get).with(cookbook2_url). + expect(api_service).to receive(:get).with(cookbook2_url). and_return(example2_cookbook_data) expect(Chef::CookbookVersion).to receive(:from_cb_artifact_data).with(example1_cookbook_data). diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb index 79ec05ea6d..b8cf7f5d1b 100644 --- a/spec/unit/property_spec.rb +++ b/spec/unit/property_spec.rb @@ -522,22 +522,41 @@ describe "Chef::Resource.property" do end end - context "hash default" do - context "(deprecations allowed)" do - before { Chef::Config[:treat_deprecation_warnings_as_errors] = false } + context "string default" do + with_property ":x, default: ''" do + it "when x is not set, it returns ''" do + expect(resource.x).to eq "" + end + it "x is immutable" do + expect { resource.x << "foo" }.to raise_error(RuntimeError, "can't modify frozen String") + end + end - with_property ":x, default: {}" do - it "when x is not set, it returns {}" do - expect(resource.x).to eq({}) - end - it "The same exact value is returned multiple times in a row" do - value = resource.x - expect(value).to eq({}) - expect(resource.x.object_id).to eq(value.object_id) - end - it "Multiple instances of x receive the exact same value" do - expect(resource.x.object_id).to eq(resource_class.new("blah2").x.object_id) - end + with_property ":x, default: lazy { '' }" do + it "x is immutable" do + expect(resource.x).to eq "" + resource.x << "foo" + expect(resource.x).to eq "foo" + expect(resource_class.new("other").x).to eq "" + end + end + end + + context "hash default" do + with_property ":x, default: {}" do + it "when x is not set, it returns {}" do + expect(resource.x).to eq({}) + end + it "x is immutable" do + expect { resource.x["foo"] = "bar" }.to raise_error(RuntimeError, "can't modify frozen Hash") + end + it "The same exact value is returned multiple times in a row" do + value = resource.x + expect(value).to eq({}) + expect(resource.x.object_id).to eq(value.object_id) + end + it "Multiple instances of x receive the exact same value" do + expect(resource.x.object_id).to eq(resource_class.new("blah2").x.object_id) end end @@ -545,17 +564,34 @@ describe "Chef::Resource.property" do it "when x is not set, it returns {}" do expect(resource.x).to eq({}) end - # it "The value is different each time it is called" do - # value = resource.x - # expect(value).to eq({}) - # expect(resource.x.object_id).not_to eq(value.object_id) - # end + it "The value is the same each time it is called" do + value = resource.x + expect(value).to eq({}) + expect(resource.x.object_id).to eq(value.object_id) + end it "Multiple instances of x receive different values" do expect(resource.x.object_id).not_to eq(resource_class.new("blah2").x.object_id) end end end + context "complex, nested default" do + with_property ":x, default: [{foo: 'bar'}]" do + it "when x is not set, it returns [{foo: 'bar'}]" do + expect(resource.x).to eq([{ foo: "bar" }]) + end + it "x is immutable" do + expect { resource.x << :other }.to raise_error(RuntimeError, "can't modify frozen Array") + end + it "x.first is immutable" do + expect { resource.x.first[:foo] = "other" }.to raise_error(RuntimeError, "can't modify frozen Hash") + end + it "x.first[:foo] is immutable" do + expect { resource.x.first[:foo] << "other" }.to raise_error(RuntimeError, "can't modify frozen String") + end + end + end + context "with a class with 'blah' as both class and instance methods" do before do resource_class.class_eval do @@ -576,7 +612,6 @@ describe "Chef::Resource.property" do it "x is run in the context of each instance it is run in" do expect(resource.x).to eq "blah1" expect(resource_class.new("another").x).to eq "another2" - # expect(resource.x).to eq "blah3" end end @@ -587,7 +622,6 @@ describe "Chef::Resource.property" do it "x is passed the value of each instance it is run in" do expect(resource.x).to eq "classblah1" expect(resource_class.new("another").x).to eq "classanother2" - # expect(resource.x).to eq "classblah3" end end end diff --git a/spec/unit/provider/cron_spec.rb b/spec/unit/provider/cron_spec.rb index 64916ef454..ef629ebd52 100644 --- a/spec/unit/provider/cron_spec.rb +++ b/spec/unit/provider/cron_spec.rb @@ -880,8 +880,7 @@ MAILTO=foo@example.com describe "read_crontab" do before :each do - @status = double("Status", :exitstatus => 0) - @stdout = StringIO.new(<<-CRONTAB) + @stdout = <<-CRONTAB 0 2 * * * /some/other/command # Chef Name: something else @@ -889,11 +888,12 @@ MAILTO=foo@example.com # Another comment CRONTAB - allow(@provider).to receive(:popen4).and_yield(1234, StringIO.new, @stdout, StringIO.new).and_return(@status) + @status = double("Status", exitstatus: 0, stdout: @stdout) + allow(@provider).to receive(:shell_out!).and_return(@status) end it "should call crontab -l with the user" do - expect(@provider).to receive(:popen4).with("crontab -l -u #{@new_resource.user}").and_return(@status) + expect(@provider).to receive(:shell_out!).with("crontab -l -u #{@new_resource.user}", returns: [0, 1]).and_return(@status) @provider.send(:read_crontab) end @@ -910,60 +910,36 @@ MAILTO=foo@example.com end it "should return nil if the user has no crontab" do - status = double("Status", :exitstatus => 1) - allow(@provider).to receive(:popen4).and_return(status) + @status = double("Status", exitstatus: 1, stdout: "") + allow(@provider).to receive(:shell_out!).and_return(@status) expect(@provider.send(:read_crontab)).to eq(nil) end it "should raise an exception if another error occurs" do - status = double("Status", :exitstatus => 2) - allow(@provider).to receive(:popen4).and_return(status) - expect do - @provider.send(:read_crontab) - end.to raise_error(Chef::Exceptions::Cron, "Error determining state of #{@new_resource.name}, exit: 2") + @status = double("Status", exitstatus: 2) + allow(@provider).to receive(:shell_out!).and_raise(Mixlib::ShellOut::ShellCommandFailed) + expect { @provider.send(:read_crontab) }.to raise_error(Chef::Exceptions::Cron) end end describe "write_crontab" do before :each do - @status = double("Status", :exitstatus => 0) - @stdin = StringIO.new - allow(@provider).to receive(:popen4).and_yield(1234, @stdin, StringIO.new, StringIO.new).and_return(@status) + @status = double("Status", exitstatus: 0) + allow(@provider).to receive(:shell_out!).and_return(@status) end it "should call crontab for the user" do - expect(@provider).to receive(:popen4).with("crontab -u #{@new_resource.user} -", :waitlast => true).and_return(@status) + expect(@provider).to receive(:shell_out!).with("crontab -u #{@new_resource.user} -", input: "Foo").and_return(@status) @provider.send(:write_crontab, "Foo") end - it "should write the given string to the crontab command" do - @provider.send(:write_crontab, "Foo\n# wibble\n wah!!") - expect(@stdin.string).to eq("Foo\n# wibble\n wah!!") - end - it "should raise an exception if the command returns non-zero" do - allow(@status).to receive(:exitstatus).and_return(1) - expect do - @provider.send(:write_crontab, "Foo") - end.to raise_error(Chef::Exceptions::Cron, "Error updating state of #{@new_resource.name}, exit: 1") - end - - it "should raise an exception if the command die's and parent tries to write" do - class WriteErrPipe - def write(str) - raise Errno::EPIPE, "Test" - end - end - allow(@status).to receive(:exitstatus).and_return(1) - allow(@provider).to receive(:popen4).and_yield(1234, WriteErrPipe.new, StringIO.new, StringIO.new).and_return(@status) - - expect(Chef::Log).to receive(:debug).with("Broken pipe - Test") - + @status = double("Status", exitstatus: 1) + allow(@provider).to receive(:shell_out!).and_raise(Mixlib::ShellOut::ShellCommandFailed) expect do @provider.send(:write_crontab, "Foo") - end.to raise_error(Chef::Exceptions::Cron, "Error updating state of #{@new_resource.name}, exit: 1") + end.to raise_error(Chef::Exceptions::Cron) end - end describe "weekday_in_crontab" do diff --git a/spec/unit/provider/erl_call_spec.rb b/spec/unit/provider/erl_call_spec.rb index f1c229028a..b5d3ae8c07 100644 --- a/spec/unit/provider/erl_call_spec.rb +++ b/spec/unit/provider/erl_call_spec.rb @@ -31,11 +31,8 @@ describe Chef::Provider::ErlCall do @provider = Chef::Provider::ErlCall.new(@new_resource, @run_context) - allow(@provider).to receive(:popen4).and_return(@status) - @stdin = StringIO.new - @stdout = StringIO.new("{ok, woohoo}") - @stderr = StringIO.new - @pid = 2342999 + @status = double("Status", stdout: "{ok, woohoo}", stderr: "") + allow(@provider).to receive(:shell_out!).and_return(@status) end it "should return a Chef::Provider::ErlCall object" do @@ -56,12 +53,9 @@ describe Chef::Provider::ErlCall do it "should write to stdin of the erl_call command" do expected_cmd = "erl_call -e -s -sname chef@localhost -c nomnomnom" - expect(@provider).to receive(:popen4).with(expected_cmd, :waitlast => true).and_return([@pid, @stdin, @stdout, @stderr]) - expect(Process).to receive(:wait).with(@pid) + expect(@provider).to receive(:shell_out!).with(expected_cmd, input: @new_resource.code).and_return(@status) @provider.action_run - - expect(@stdin.string).to eq("#{@new_resource.code}\n") end end @@ -73,12 +67,10 @@ describe Chef::Provider::ErlCall do end it "should write to stdin of the erl_call command" do - expect(@provider).to receive(:popen4).with("erl_call -e -name chef@localhost ", :waitlast => true).and_return([@pid, @stdin, @stdout, @stderr]) - expect(Process).to receive(:wait).with(@pid) + expected_cmd = "erl_call -e -name chef@localhost " + expect(@provider).to receive(:shell_out!).with(expected_cmd, input: @new_resource.code).and_return(@status) @provider.action_run - - expect(@stdin.string).to eq("#{@new_resource.code}\n") end end diff --git a/spec/unit/provider/package/rubygems_spec.rb b/spec/unit/provider/package/rubygems_spec.rb index 53c82f2f70..c40eed50cf 100644 --- a/spec/unit/provider/package/rubygems_spec.rb +++ b/spec/unit/provider/package/rubygems_spec.rb @@ -341,6 +341,7 @@ describe Chef::Provider::Package::Rubygems do let(:bindir) { "/usr/bin/ruby" } let(:options) { nil } let(:source) { nil } + let(:include_default_source) { true } let(:new_resource) do new_resource = Chef::Resource::GemPackage.new(gem_name) @@ -348,6 +349,7 @@ describe Chef::Provider::Package::Rubygems do new_resource.gem_binary(gem_binary) if gem_binary new_resource.options(options) if options new_resource.source(source) if source + new_resource.include_default_source(include_default_source) new_resource end @@ -534,17 +536,56 @@ describe Chef::Provider::Package::Rubygems do end end + context "when the source is from the rubygems_uri" do + it "determines the candidate version by querying the remote gem servers" do + Chef::Config[:rubygems_url] = "https://mirror1/" + expect(provider.gem_env).to receive(:candidate_version_from_remote) + .with(gem_dep, "https://mirror1/") + .and_return(Gem::Version.new(target_version)) + expect(provider.candidate_version).to eq(target_version) + end + end + context "when the requested source is a remote server" do let(:source) { "http://mygems.example.com" } it "determines the candidate version by querying the remote gem servers" do expect(provider.gem_env).to receive(:candidate_version_from_remote) + .with(gem_dep, source, Chef::Config[:rubygems_url]) + .and_return(Gem::Version.new(target_version)) + expect(provider.candidate_version).to eq(target_version) + end + + it "overwrites the config variable" do + new_resource.include_default_source false + Chef::Config[:rubygems_url] = "https://overridden" + expect(provider.gem_env).to receive(:candidate_version_from_remote) .with(gem_dep, source) .and_return(Gem::Version.new(target_version)) expect(provider.candidate_version).to eq(target_version) end end + context "when the requested source is an array" do + let(:source) { [ "https://mirror1", "https://mirror2" ] } + + it "determines the candidate version by querying the remote gem servers" do + expect(provider.gem_env).to receive(:candidate_version_from_remote) + .with(gem_dep, *[source, Chef::Config[:rubygems_url] ].flatten) + .and_return(Gem::Version.new(target_version)) + expect(provider.candidate_version).to eq(target_version) + end + + it "overwrites the config variable" do + new_resource.include_default_source false + Chef::Config[:rubygems_url] = "https://overridden" + expect(provider.gem_env).to receive(:candidate_version_from_remote) + .with(gem_dep, *source) + .and_return(Gem::Version.new(target_version)) + expect(provider.candidate_version).to eq(target_version) + end + end + context "when the requested source is a file" do let(:gem_name) { "chef-integration-test" } let(:source) { CHEF_SPEC_DATA + "/gems/chef-integration-test-0.1.0.gem" } @@ -566,18 +607,17 @@ describe Chef::Provider::Package::Rubygems do current_resource end + let(:version) { Gem::Version.new(candidate_version) } + before do - version = Gem::Version.new(candidate_version) - args = [gem_dep] - args << source if source - allow(provider.gem_env).to receive(:candidate_version_from_remote) - .with(*args) - .and_return(version) + expected_source = [ source ] + expected_source << Chef::Config[:rubygems_url] if include_default_source + allow(provider.gem_env).to receive(:candidate_version_from_remote).with(gem_dep, *expected_source.flatten.compact).and_return(version) end describe "in the current gem environment" do it "installs the gem via the gems api when no explicit options are used" do - expect(provider.gem_env).to receive(:install).with(gem_dep, sources: nil) + expect(provider.gem_env).to receive(:install).with(gem_dep, sources: [ Chef::Config[:rubygems_url] ]) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end @@ -586,7 +626,7 @@ describe Chef::Provider::Package::Rubygems do let(:source) { "http://gems.example.org" } it "installs the gem via the gems api" do - expect(provider.gem_env).to receive(:install).with(gem_dep, sources: [source]) + expect(provider.gem_env).to receive(:install).with(gem_dep, sources: [source, Chef::Config[:rubygems_url]]) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end @@ -617,7 +657,7 @@ describe Chef::Provider::Package::Rubygems do it "installs the gem via the gems api, when the package has no file separator characters in it, but a matching file exists in cwd" do allow(::File).to receive(:exist?).and_return(true) new_resource.package_name("rspec-core") - expect(provider.gem_env).to receive(:install).with(gem_dep, sources: nil) + expect(provider.gem_env).to receive(:install).with(gem_dep, sources: [ Chef::Config[:rubygems_url] ]) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end @@ -626,7 +666,20 @@ describe Chef::Provider::Package::Rubygems do let(:options) { "-i /alt/install/location" } it "installs the gem by shelling out when options are provided as a String" do - expected = "gem install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\" #{options}" + expected = "gem install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\" --source=https://www.rubygems.org #{options}" + expect(provider).to receive(:shell_out!).with(expected, env: nil, timeout: 900) + provider.run_action(:install) + expect(new_resource).to be_updated_by_last_action + end + end + + context "when the Chef::Config[:rubygems_url] option is provided" do + let(:gem_binary) { "/foo/bar" } + + it "installs the gem with rubygems.org as an added source" do + Chef::Config[:rubygems_url] = "https://mirror1" + expect(provider.gem_env).to receive(:candidate_version_from_remote).with(gem_dep, Chef::Config[:rubygems_url]).and_return(version) + expected = "#{gem_binary} install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\" --source=https://mirror1" expect(provider).to receive(:shell_out!).with(expected, env: nil, timeout: 900) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action @@ -638,11 +691,47 @@ describe Chef::Provider::Package::Rubygems do let(:gem_binary) { "/foo/bar" } it "installs the gem with rubygems.org as an added source" do - expected = "#{gem_binary} install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\" --source=#{source} --source=https://rubygems.org" + expected = "#{gem_binary} install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\" --source=#{source} --source=https://www.rubygems.org" + expect(provider).to receive(:shell_out!).with(expected, env: nil, timeout: 900) + provider.run_action(:install) + expect(new_resource).to be_updated_by_last_action + end + + context "with include_default_source false" do + let(:include_default_source) { false } + + it "ignores the Chef::Config setting" do + Chef::Config[:rubygems_url] = "https://ignored" + expected = "#{gem_binary} install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\" --source=#{source}" + expect(provider).to receive(:shell_out!).with(expected, env: nil, timeout: 900) + provider.run_action(:install) + expect(new_resource).to be_updated_by_last_action + end + end + end + + context "when the source is an array" do + let(:source) { [ "https://mirror1" , "https://mirror2" ] } + let(:gem_binary) { "/foo/bar" } + + it "installs the gem with an array as an added source" do + expected = "#{gem_binary} install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\" --source=https://mirror1 --source=https://mirror2 --source=https://www.rubygems.org" expect(provider).to receive(:shell_out!).with(expected, env: nil, timeout: 900) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end + + context "with include_default_source false" do + let(:include_default_source) { false } + + it "ignores the Chef::Config setting" do + Chef::Config[:rubygems_url] = "https://ignored" + expected = "#{gem_binary} install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\" --source=https://mirror1 --source=https://mirror2" + expect(provider).to receive(:shell_out!).with(expected, env: nil, timeout: 900) + provider.run_action(:install) + expect(new_resource).to be_updated_by_last_action + end + end end context "when we have cleared sources and an explict source is specified" do @@ -651,7 +740,7 @@ describe Chef::Provider::Package::Rubygems do it "installs the gem" do new_resource.clear_sources(true) - expected = "#{gem_binary} install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\" --clear-sources --source=#{source}" + expected = "#{gem_binary} install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\" --clear-sources --source=#{source} --source=https://www.rubygems.org" expect(provider).to receive(:shell_out!).with(expected, env: nil, timeout: 900) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action @@ -663,7 +752,7 @@ describe Chef::Provider::Package::Rubygems do let(:options) { "-i /alt/install/location" } it "installs the gem by shelling out when options are provided but no version is given" do - expected = "gem install rspec-core -q --no-rdoc --no-ri -v \"#{candidate_version}\" #{options}" + expected = "gem install rspec-core -q --no-rdoc --no-ri -v \"#{candidate_version}\" --source=https://www.rubygems.org #{options}" expect(provider).to receive(:shell_out!).with(expected, env: nil, timeout: 900) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action @@ -674,7 +763,7 @@ describe Chef::Provider::Package::Rubygems do let(:options) { { install_dir: "/alt/install/location" } } it "installs the gem via the gems api when options are given as a Hash" do - expect(provider.gem_env).to receive(:install).with(gem_dep, { sources: nil }.merge(options)) + expect(provider.gem_env).to receive(:install).with(gem_dep, { sources: [ Chef::Config[:rubygems_url] ] }.merge(options)) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end @@ -684,7 +773,7 @@ describe Chef::Provider::Package::Rubygems do let(:target_version) { "9000.0.2" } it "installs the gem via the gems api" do - expect(provider.gem_env).to receive(:install).with(gem_dep, sources: nil) + expect(provider.gem_env).to receive(:install).with(gem_dep, sources: [ Chef::Config[:rubygems_url] ] ) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end @@ -727,7 +816,7 @@ describe Chef::Provider::Package::Rubygems do let(:gem_binary) { "/usr/weird/bin/gem" } it "installs the gem by shelling out to gem install" do - expect(provider).to receive(:shell_out!).with("#{gem_binary} install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\"", env: nil, timeout: 900) + expect(provider).to receive(:shell_out!).with("#{gem_binary} install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\" --source=https://www.rubygems.org", env: nil, timeout: 900) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end diff --git a/spec/unit/provider/package/windows/msi_spec.rb b/spec/unit/provider/package/windows/msi_spec.rb index c8099c38d0..aa528ab90e 100644 --- a/spec/unit/provider/package/windows/msi_spec.rb +++ b/spec/unit/provider/package/windows/msi_spec.rb @@ -1,6 +1,6 @@ # # Author:: Bryan McLellan <btm@loftninjas.org> -# Copyright:: Copyright 2014-2016, Chef Software, Inc. +# Copyright:: Copyright 2014-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/spec/unit/provider/package/yum_spec.rb b/spec/unit/provider/package/yum_spec.rb index e0a5f8c862..e8eb830f65 100644 --- a/spec/unit/provider/package/yum_spec.rb +++ b/spec/unit/provider/package/yum_spec.rb @@ -278,31 +278,31 @@ describe Chef::Provider::Package::Yum do end it "should flush the cache if :before is true" do - allow(@new_resource).to receive(:flush_cache).and_return({ :after => false, :before => true }) + @new_resource.flush_cache({ :after => false, :before => true }) expect(@yum_cache).to receive(:reload).once @provider.load_current_resource end it "should flush the cache if :before is false" do - allow(@new_resource).to receive(:flush_cache).and_return({ :after => false, :before => false }) + @new_resource.flush_cache({ :after => false, :before => false }) expect(@yum_cache).not_to receive(:reload) @provider.load_current_resource end it "should detect --enablerepo or --disablerepo when passed among options, collect them preserving order and notify the yum cache" do - allow(@new_resource).to receive(:options).and_return("--stuff --enablerepo=foo --otherthings --disablerepo=a,b,c --enablerepo=bar") + @new_resource.options("--stuff --enablerepo=foo --otherthings --disablerepo=a,b,c --enablerepo=bar") expect(@yum_cache).to receive(:enable_extra_repo_control).with("--enablerepo=foo --disablerepo=a,b,c --enablerepo=bar") @provider.load_current_resource end it "should let the yum cache know extra repos are disabled if --enablerepo or --disablerepo aren't among options" do - allow(@new_resource).to receive(:options).and_return("--stuff --otherthings") + @new_resource.options("--stuff --otherthings") expect(@yum_cache).to receive(:disable_extra_repo_control) @provider.load_current_resource end it "should let the yum cache know extra repos are disabled if options aren't set" do - allow(@new_resource).to receive(:options).and_return(nil) + @new_resource.options(nil) expect(@yum_cache).to receive(:disable_extra_repo_control) @provider.load_current_resource end @@ -558,7 +558,7 @@ describe Chef::Provider::Package::Yum do it "installs the package with the options given in the resource" do @provider.load_current_resource allow(@provider).to receive(:candidate_version).and_return("11") - allow(@new_resource).to receive(:options).and_return("--disablerepo epmd") + @new_resource.options("--disablerepo epmd") allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1) expect(@provider).to receive(:yum_command).with( "-d0 -e0 -y --disablerepo epmd install cups-11" @@ -2261,7 +2261,7 @@ describe "Chef::Provider::Package::Yum - Multi" do expect(@provider).to receive(:yum_command).with( "-d0 -e0 -y --disablerepo epmd install cups-1.2.4-11.19.el5 vim-1.0" ) - allow(@new_resource).to receive(:options).and_return("--disablerepo epmd") + @new_resource.options("--disablerepo epmd") @provider.install_package(%w{cups vim}, ["1.2.4-11.19.el5", "1.0"]) end diff --git a/spec/unit/provider/package/zypper_spec.rb b/spec/unit/provider/package/zypper_spec.rb index 7e6f204b64..f3c31dc730 100644 --- a/spec/unit/provider/package/zypper_spec.rb +++ b/spec/unit/provider/package/zypper_spec.rb @@ -1,6 +1,6 @@ # # Author:: Adam Jacob (<adam@chef.io>) -# Copyright:: Copyright 2008-2016, Chef Software, Inc. +# Copyright:: Copyright 2008-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -111,23 +111,29 @@ describe Chef::Provider::Package::Zypper do describe "install_package" do it "should run zypper install with the package name and version" do - allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(true) shell_out_expectation!( "zypper", "--non-interactive", "install", "--auto-agree-with-licenses", "emacs=1.0" ) provider.install_package(["emacs"], ["1.0"]) end - it "should run zypper install without gpg checks" do - allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false) + + it "should run zypper install with gpg checks" do shell_out_expectation!( - "zypper", "--non-interactive", "--no-gpg-checks", "install", "--auto-agree-with-licenses", "emacs=1.0" + "zypper", "--non-interactive", "install", "--auto-agree-with-licenses", "emacs=1.0" ) provider.install_package(["emacs"], ["1.0"]) end - it "should warn about gpg checks on zypper install" do - expect(Chef::Log).to receive(:warn).with( - /All packages will be installed without gpg signature checks/ + + it "setting the property should disable gpg checks" do + new_resource.gpg_check false + shell_out_expectation!( + "zypper", "--non-interactive", "--no-gpg-checks", "install", "--auto-agree-with-licenses", "emacs=1.0" ) + provider.install_package(["emacs"], ["1.0"]) + end + + it "setting the config variable should disable gpg checks" do + Chef::Config[:zypper_check_gpg] = false shell_out_expectation!( "zypper", "--non-interactive", "--no-gpg-checks", "install", "--auto-agree-with-licenses", "emacs=1.0" ) @@ -137,29 +143,20 @@ describe Chef::Provider::Package::Zypper do describe "upgrade_package" do it "should run zypper update with the package name and version" do - allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(true) shell_out_expectation!( "zypper", "--non-interactive", "install", "--auto-agree-with-licenses", "emacs=1.0" ) provider.upgrade_package(["emacs"], ["1.0"]) end - it "should run zypper update without gpg checks" do - allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false) - shell_out_expectation!( - "zypper", "--non-interactive", "--no-gpg-checks", "install", "--auto-agree-with-licenses", "emacs=1.0" - ) - provider.upgrade_package(["emacs"], ["1.0"]) - end - it "should warn about gpg checks on zypper upgrade" do - expect(Chef::Log).to receive(:warn).with( - /All packages will be installed without gpg signature checks/ - ) + it "should run zypper update without gpg checks when setting the property" do + new_resource.gpg_check false shell_out_expectation!( "zypper", "--non-interactive", "--no-gpg-checks", "install", "--auto-agree-with-licenses", "emacs=1.0" ) provider.upgrade_package(["emacs"], ["1.0"]) end - it "should run zypper upgrade without gpg checks" do + it "should run zypper update without gpg checks when setting the config variable" do + Chef::Config[:zypper_check_gpg] = false shell_out_expectation!( "zypper", "--non-interactive", "--no-gpg-checks", "install", "--auto-agree-with-licenses", "emacs=1.0" ) @@ -171,7 +168,6 @@ describe Chef::Provider::Package::Zypper do context "when package version is not explicitly specified" do it "should run zypper remove with the package name" do - allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(true) shell_out_expectation!( "zypper", "--non-interactive", "remove", "emacs" ) @@ -181,25 +177,22 @@ describe Chef::Provider::Package::Zypper do context "when package version is explicitly specified" do it "should run zypper remove with the package name" do - allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(true) shell_out_expectation!( "zypper", "--non-interactive", "remove", "emacs=1.0" ) provider.remove_package(["emacs"], ["1.0"]) end it "should run zypper remove without gpg checks" do - allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false) + new_resource.gpg_check false shell_out_expectation!( "zypper", "--non-interactive", "--no-gpg-checks", "remove", "emacs=1.0" ) provider.remove_package(["emacs"], ["1.0"]) end - it "should warn about gpg checks on zypper remove" do - expect(Chef::Log).to receive(:warn).with( - /All packages will be installed without gpg signature checks/ - ) + it "should run zypper remove without gpg checks when the config is false" do + Chef::Config[:zypper_check_gpg] = false shell_out_expectation!( - "zypper", "--non-interactive", "--no-gpg-checks", "remove", "emacs=1.0" + "zypper", "--non-interactive", "--no-gpg-checks", "remove", "emacs=1.0" ) provider.remove_package(["emacs"], ["1.0"]) end @@ -209,21 +202,19 @@ describe Chef::Provider::Package::Zypper do describe "purge_package" do it "should run remove with the name and version and --clean-deps" do shell_out_expectation!( - "zypper", "--non-interactive", "--no-gpg-checks", "remove", "--clean-deps", "emacs=1.0" + "zypper", "--non-interactive", "remove", "--clean-deps", "emacs=1.0" ) provider.purge_package(["emacs"], ["1.0"]) end it "should run zypper purge without gpg checks" do - allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false) + new_resource.gpg_check false shell_out_expectation!( "zypper", "--non-interactive", "--no-gpg-checks", "remove", "--clean-deps", "emacs=1.0" ) provider.purge_package(["emacs"], ["1.0"]) end - it "should warn about gpg checks on zypper purge" do - expect(Chef::Log).to receive(:warn).with( - /All packages will be installed without gpg signature checks/ - ) + it "should run zypper purge without gpg checks when the config is false" do + Chef::Config[:zypper_check_gpg] = false shell_out_expectation!( "zypper", "--non-interactive", "--no-gpg-checks", "remove", "--clean-deps", "emacs=1.0" ) @@ -233,29 +224,13 @@ describe Chef::Provider::Package::Zypper do describe "lock_package" do it "should run zypper addlock with the package name" do - allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(true) shell_out_expectation!( "zypper", "--non-interactive", "addlock", "emacs" ) provider.lock_package(["emacs"], [nil]) end it "should run zypper addlock without gpg checks" do - allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false) - shell_out_expectation!( - "zypper", "--non-interactive", "--no-gpg-checks", "addlock", "emacs" - ) - provider.lock_package(["emacs"], [nil]) - end - it "should warn about gpg checks on zypper addlock" do - expect(Chef::Log).to receive(:warn).with( - /All packages will be installed without gpg signature checks/ - ) - shell_out_expectation!( - "zypper", "--non-interactive", "--no-gpg-checks", "addlock", "emacs" - ) - provider.lock_package(["emacs"], [nil]) - end - it "should run zypper addlock without gpg checks" do + new_resource.gpg_check false shell_out_expectation!( "zypper", "--non-interactive", "--no-gpg-checks", "addlock", "emacs" ) @@ -265,29 +240,13 @@ describe Chef::Provider::Package::Zypper do describe "unlock_package" do it "should run zypper removelock with the package name" do - allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(true) shell_out_expectation!( "zypper", "--non-interactive", "removelock", "emacs" ) provider.unlock_package(["emacs"], [nil]) end it "should run zypper removelock without gpg checks" do - allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false) - shell_out_expectation!( - "zypper", "--non-interactive", "--no-gpg-checks", "removelock", "emacs" - ) - provider.unlock_package(["emacs"], [nil]) - end - it "should warn about gpg checks on zypper removelock" do - expect(Chef::Log).to receive(:warn).with( - /All packages will be installed without gpg signature checks/ - ) - shell_out_expectation!( - "zypper", "--non-interactive", "--no-gpg-checks", "removelock", "emacs" - ) - provider.unlock_package(["emacs"], [nil]) - end - it "should run zypper removelock without gpg checks" do + new_resource.gpg_check false shell_out_expectation!( "zypper", "--non-interactive", "--no-gpg-checks", "removelock", "emacs" ) @@ -303,7 +262,7 @@ describe Chef::Provider::Package::Zypper do describe "install_package" do it "should run zypper install with the package name and version" do shell_out_expectation!( - "zypper", "--no-gpg-checks", "install", "--auto-agree-with-licenses", "-y", "emacs" + "zypper", "install", "--auto-agree-with-licenses", "-y", "emacs" ) provider.install_package(["emacs"], ["1.0"]) end @@ -312,7 +271,7 @@ describe Chef::Provider::Package::Zypper do describe "upgrade_package" do it "should run zypper update with the package name and version" do shell_out_expectation!( - "zypper", "--no-gpg-checks", "install", "--auto-agree-with-licenses", "-y", "emacs" + "zypper", "install", "--auto-agree-with-licenses", "-y", "emacs" ) provider.upgrade_package(["emacs"], ["1.0"]) end @@ -321,7 +280,7 @@ describe Chef::Provider::Package::Zypper do describe "remove_package" do it "should run zypper remove with the package name" do shell_out_expectation!( - "zypper", "--no-gpg-checks", "remove", "-y", "emacs" + "zypper", "remove", "-y", "emacs" ) provider.remove_package(["emacs"], ["1.0"]) end @@ -330,17 +289,15 @@ describe Chef::Provider::Package::Zypper do describe "when installing multiple packages" do # https://github.com/chef/chef/issues/3570 it "should install an array of package names and versions" do - allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false) shell_out_expectation!( - "zypper", "--non-interactive", "--no-gpg-checks", "install", "--auto-agree-with-licenses", "emacs=1.0", "vim=2.0" + "zypper", "--non-interactive", "install", "--auto-agree-with-licenses", "emacs=1.0", "vim=2.0" ) provider.install_package(%w{emacs vim}, ["1.0", "2.0"]) end it "should remove an array of package names and versions" do - allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false) shell_out_expectation!( - "zypper", "--non-interactive", "--no-gpg-checks", "remove", "emacs=1.0", "vim=2.0" + "zypper", "--non-interactive", "remove", "emacs=1.0", "vim=2.0" ) provider.remove_package(%w{emacs vim}, ["1.0", "2.0"]) end diff --git a/spec/unit/provider/service/debian_service_spec.rb b/spec/unit/provider/service/debian_service_spec.rb index 799ed991a3..d944192755 100644 --- a/spec/unit/provider/service/debian_service_spec.rb +++ b/spec/unit/provider/service/debian_service_spec.rb @@ -59,11 +59,10 @@ describe Chef::Provider::Service::Debian do /etc/rc6.d/K20chef UPDATE_RC_D_SUCCESS - @stdout = StringIO.new(result) - @stderr = StringIO.new - @status = double("Status", :exitstatus => 0, :stdout => @stdout) + @stdout = result + @stderr = "" + @status = double("Status", :exitstatus => 0, :stdout => @stdout, :stderr => @stderr) allow(@provider).to receive(:shell_out!).and_return(@status) - allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) end it "says the service is enabled" do @@ -80,13 +79,9 @@ describe Chef::Provider::Service::Debian do context "when update-rc.d shows init isn't linked to rc*.d/" do before do allow(@provider).to receive(:assert_update_rcd_available) - @status = double("Status", :exitstatus => 0) - @stdout = StringIO.new( - " Removing any system startup links for /etc/init.d/chef ...") - @stderr = StringIO.new - @status = double("Status", :exitstatus => 0, :stdout => @stdout) + @stdout = " Removing any system startup links for /etc/init.d/chef ..." + @status = double("Status", :exitstatus => 0, :stdout => @stdout, stderr: "") allow(@provider).to receive(:shell_out!).and_return(@status) - allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) end it "says the service is disabled" do @@ -102,11 +97,12 @@ describe Chef::Provider::Service::Debian do context "when update-rc.d fails" do before do - @status = double("Status", :exitstatus => -1) - allow(@provider).to receive(:popen4).and_return(@status) + @status = double("Status", exitstatus: -1, stdout: "", stderr: "") + allow(@provider).to receive(:shell_out!).and_return(@status) end it "raises an error" do + @provider.load_current_resource @provider.define_resource_requirements expect do @provider.process_resource_requirements @@ -198,11 +194,10 @@ insserv: dryrun, not creating .depend.boot, .depend.start, and .depend.stop before do allow(@provider).to receive(:assert_update_rcd_available) - @stdout = StringIO.new(expected_results["linked"]["stdout"]) - @stderr = StringIO.new(expected_results["linked"]["stderr"]) - @status = double("Status", :exitstatus => 0, :stdout => @stdout) + @stdout = expected_results["linked"]["stdout"] + @stderr = expected_results["linked"]["stderr"] + @status = double("Status", exitstatus: 0, stdout: @stdout, stderr: @stderr) allow(@provider).to receive(:shell_out!).and_return(@status) - allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) end it "says the service is enabled" do @@ -224,11 +219,10 @@ insserv: dryrun, not creating .depend.boot, .depend.start, and .depend.stop context "when update-rc.d shows init isn't linked to rc*.d/" do before do allow(@provider).to receive(:assert_update_rcd_available) - @stdout = StringIO.new(expected_results["not linked"]["stdout"]) - @stderr = StringIO.new(expected_results["not linked"]["stderr"]) - @status = double("Status", :exitstatus => 0, :stdout => @stdout) + @stdout = expected_results["not linked"]["stdout"] + @stderr = expected_results["not linked"]["stderr"] + @status = double("Status", exitstatus: 0, stdout: @stdout, stderr: @stderr) allow(@provider).to receive(:shell_out!).and_return(@status) - allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) end it "says the service is disabled" do diff --git a/spec/unit/provider/service/upstart_service_spec.rb b/spec/unit/provider/service/upstart_service_spec.rb index 0245dd038c..5c47259f52 100644 --- a/spec/unit/provider/service/upstart_service_spec.rb +++ b/spec/unit/provider/service/upstart_service_spec.rb @@ -71,12 +71,8 @@ describe Chef::Provider::Service::Upstart do @current_resource = Chef::Resource::Service.new("rsyslog") allow(Chef::Resource::Service).to receive(:new).and_return(@current_resource) - @status = double("Status", :exitstatus => 0) - allow(@provider).to receive(:popen4).and_return(@status) - @stdin = StringIO.new - @stdout = StringIO.new - @stderr = StringIO.new - @pid = double("PID") + @status = double("Status", exitstatus: 0, stdout: "", stderr: "") + allow(@provider).to receive(:shell_out).and_return(@status) allow(::File).to receive(:exists?).and_return(true) allow(::File).to receive(:open).and_return(true) @@ -100,28 +96,25 @@ describe Chef::Provider::Service::Upstart do end it "should run '/sbin/status rsyslog'" do - expect(@provider).to receive(:popen4).with("/sbin/status rsyslog").and_return(@status) + expect(@provider).to receive(:shell_out).with("/sbin/status rsyslog").and_return(@status) @provider.load_current_resource end describe "when the status command uses the new format" do it "should set running to true if the goal state is 'start'" do - @stdout = StringIO.new("rsyslog start/running") - allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) + expect(@status).to receive(:stdout).and_return("rsyslog start/running") @provider.load_current_resource expect(@current_resource.running).to be_truthy end it "should set running to true if the goal state is 'start' but current state is not 'running'" do - @stdout = StringIO.new("rsyslog start/starting") - allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) + expect(@status).to receive(:stdout).and_return("rsyslog start/starting") @provider.load_current_resource expect(@current_resource.running).to be_truthy end it "should set running to false if the goal state is 'stop'" do - @stdout = StringIO.new("rsyslog stop/waiting") - allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) + expect(@status).to receive(:stdout).and_return("rsyslog stop/waiting") @provider.load_current_resource expect(@current_resource.running).to be_falsey end @@ -129,22 +122,19 @@ describe Chef::Provider::Service::Upstart do describe "when the status command uses the new format with an instance" do it "should set running to true if the goal state is 'start'" do - @stdout = StringIO.new("rsyslog (test) start/running, process 100") - allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) + expect(@status).to receive(:stdout).and_return("rsyslog (test) start/running, process 100") @provider.load_current_resource expect(@current_resource.running).to be_truthy end it "should set running to true if the goal state is 'start' but current state is not 'running'" do - @stdout = StringIO.new("rsyslog (test) start/starting, process 100") - allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) + expect(@status).to receive(:stdout).and_return("rsyslog (test) start/starting, process 100") @provider.load_current_resource expect(@current_resource.running).to be_truthy end it "should set running to false if the goal state is 'stop'" do - @stdout = StringIO.new("rsyslog (test) stop/waiting, process 100") - allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) + expect(@status).to receive(:stdout).and_return("rsyslog (test) stop/waiting, process 100") @provider.load_current_resource expect(@current_resource.running).to be_falsey end @@ -152,22 +142,19 @@ describe Chef::Provider::Service::Upstart do describe "when the status command uses the old format" do it "should set running to true if the goal state is 'start'" do - @stdout = StringIO.new("rsyslog (start) running, process 32225") - allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) + expect(@status).to receive(:stdout).and_return("rsyslog (start) running, process 32225") @provider.load_current_resource expect(@current_resource.running).to be_truthy end it "should set running to true if the goal state is 'start' but current state is not 'running'" do - @stdout = StringIO.new("rsyslog (start) starting, process 32225") - allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) + expect(@status).to receive(:stdout).and_return("rsyslog (start) starting, process 32225") @provider.load_current_resource expect(@current_resource.running).to be_truthy end it "should set running to false if the goal state is 'stop'" do - @stdout = StringIO.new("rsyslog (stop) waiting") - allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) + expect(@status).to receive(:stdout).and_return("rsyslog (stop) waiting") @provider.load_current_resource expect(@current_resource.running).to be_falsey end diff --git a/spec/unit/provider/subversion_spec.rb b/spec/unit/provider/subversion_spec.rb index a4ab4ae42c..55d6dc5d24 100644 --- a/spec/unit/provider/subversion_spec.rb +++ b/spec/unit/provider/subversion_spec.rb @@ -1,6 +1,6 @@ # # Author:: Daniel DeLeo (<dan@kallistec.com>) -# Copyright:: Copyright 2008-2016, Chef Software Inc. +# Copyright:: Copyright 2008-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -43,7 +43,7 @@ describe Chef::Provider::Subversion do ENV.update(@original_env) end - it "converts resource attributes to options for run_command and popen4" do + it "converts resource attributes to options for shell_out" do expect(@provider.run_options).to eq({}) @resource.user "deployninja" expect(@provider.run_options).to eq({ :user => "deployninja" }) diff --git a/spec/unit/provider/user/pw_spec.rb b/spec/unit/provider/user/pw_spec.rb index 3637ce0b95..913b0ae0ab 100644 --- a/spec/unit/provider/user/pw_spec.rb +++ b/spec/unit/provider/user/pw_spec.rb @@ -155,11 +155,7 @@ describe Chef::Provider::User::Pw do describe "when modifying the password" do before(:each) do @status = double("Status", exitstatus: 0) - allow(@provider).to receive(:popen4).and_return(@status) - @pid = nil - @stdin = nil - @stdout = nil - @stderr = nil + allow(@provider).to receive(:shell_out!).and_return(@status) end describe "and the new password has not been specified" do @@ -206,24 +202,16 @@ describe Chef::Provider::User::Pw do end it "should run pw usermod with the username and the option -H 0" do - expect(@provider).to receive(:popen4).with("pw usermod adam -H 0", waitlast: true).and_return(@status) + expect(@provider).to receive(:shell_out!).with("pw usermod adam -H 0", { :input => "abracadabra" }).and_return(@status) @provider.modify_password end - it "should send the new password to the stdin of pw usermod" do - @stdin = StringIO.new - allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) - @provider.modify_password - expect(@stdin.string).to eq("abracadabra\n") - end - it "should raise an exception if pw usermod fails" do - expect(@status).to receive(:exitstatus).and_return(1) - expect { @provider.modify_password }.to raise_error(Chef::Exceptions::User) + expect(@provider).to receive(:shell_out!).and_raise(Mixlib::ShellOut::ShellCommandFailed) + expect { @provider.modify_password }.to raise_error(Mixlib::ShellOut::ShellCommandFailed) end it "should not raise an exception if pw usermod succeeds" do - expect(@status).to receive(:exitstatus).and_return(0) expect { @provider.modify_password }.not_to raise_error end end diff --git a/spec/unit/provider/windows_task_spec.rb b/spec/unit/provider/windows_task_spec.rb new file mode 100644 index 0000000000..80038aa6db --- /dev/null +++ b/spec/unit/provider/windows_task_spec.rb @@ -0,0 +1,392 @@ +# +# Author:: Nimisha Sharad (<nimisha.sharad@msystechnologies.com>) +# Copyright:: Copyright 2008-2016, Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "spec_helper" + +describe Chef::Provider::WindowsTask do + let(:new_resource) { Chef::Resource::WindowsTask.new("sample_task") } + + let(:provider) do + node = Chef::Node.new + events = Chef::EventDispatch::Dispatcher.new + run_context = Chef::RunContext.new(node, {}, events) + Chef::Provider::WindowsTask.new(new_resource, run_context) + end + + let(:task_hash) do + { + :"" => "", + :Folder => "\\", + :HostName => "NIMISHA-PC", + :TaskName => "\\sample_task", + :NextRunTime => "3/30/2017 2:42:00 PM", + :Status => "Ready", + :LogonMode => "Interactive/Background", + :LastRunTime => "3/30/2017 2:27:00 PM", + :LastResult => "1", + :Author => "Administrator", + :TaskToRun => "chef-client", + :StartIn => "N/A", + :Comment => "N/A", + :ScheduledTaskState => "Enabled", + :IdleTime => "Disabled", + :PowerManagement => "Stop On Battery Mode, No Start On Batteries", + :RunAsUser => "SYSTEM", + :DeleteTaskIfNotRescheduled => "Enabled", + :StopTaskIfRunsXHoursandXMins => "72:00:00", + :Schedule => "Scheduling data is not available in this format.", + :ScheduleType => "One Time Only, Minute", + :StartTime => "1:12:00 PM", + :StartDate => "3/30/2017", + :EndDate => "N/A", + :Days => "N/A", + :Months => "N/A", + :"Repeat:Every" => "0 Hour(s), 15 Minute(s)", + :"Repeat:Until:Time" => "None", + :"Repeat:Until:Duration" => "Disabled", + :"Repeat:StopIfStillRunning" => "Disabled", + :run_level => "HighestAvailable", + :repetition_interval => "PT15M", + :execution_time_limit => "PT72H", + } + end + + let(:task_xml) do + "<?xml version=\"1.0\" encoding=\"UTF-16\"?>\r\r\n<Task version=\"1.2\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\">\r\r\n <RegistrationInfo>\r\r\n <Date>2017-03-31T15:34:44</Date>\r\r\n <Author>Administrator</Author>\r\r\n </RegistrationInfo>\r\r\n<Triggers>\r\r\n <TimeTrigger>\r\r\n <Repetition>\r\r\n <Interval>PT15M</Interval>\r\r\n <StopAtDurationEnd>false</StopAtDurationEnd>\r\r\n </Repetition>\r\r\n <StartBoundary>2017-03-31T15:34:00</StartBoundary>\r\r\n <Enabled>true</Enabled>\r\r\n </TimeTrigger>\r\r\n </Triggers>\r\r\n <Principals>\r\r\n <Principal id=\"Author\">\r\r\n <RunLevel>HighestAvailable</RunLevel>\r\r\n <UserId>S-1-5-18</UserId>\r\r\n </Principal>\r\r\n </Principals>\r\r\n <Settings>\r\r\n <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>\r\r\n <DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries>\r\r\n <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>\r\r\n <AllowHardTerminate>true</AllowHardTerminate>\r\r\n <StartWhenAvailable>false</StartWhenAvailable>\r\r\n <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>\r\r\n <IdleSettings>\r\r\n <Duration>PT10M</Duration>\r\r\n<WaitTimeout>PT1H</WaitTimeout>\r\r\n <StopOnIdleEnd>true</StopOnIdleEnd>\r\r\n <RestartOnIdle>false</RestartOnIdle>\r\r\n </IdleSettings>\r\r\n <AllowStartOnDemand>true</AllowStartOnDemand>\r\r\n <Enabled>true</Enabled>\r\r\n <Hidden>false</Hidden>\r\r\n<RunOnlyIfIdle>false</RunOnlyIfIdle>\r\r\n <WakeToRun>false</WakeToRun>\r\r\n <ExecutionTimeLimit>PT72H</ExecutionTimeLimit>\r\r\n<Priority>7</Priority>\r\r\n </Settings>\r\r\n <Actions Context=\"Author\">\r\r\n <Exec>\r\r\n <Command>chef-client</Command>\r\r\n </Exec>\r\r\n </Actions>\r\r\n</Task>" + end + + describe "#load_current_resource" do + it "returns a current_resource" do + allow(provider).to receive(:load_task_hash) + expect(provider.load_current_resource).to be_kind_of(Chef::Resource::WindowsTask) + end + + context "if the given task name already exists" do + before do + allow(provider).to receive(:load_task_hash).and_return({ :TaskName => "\\sample_task" }) + end + + it "calls set_current_resource" do + expect(provider).to receive(:set_current_resource) + provider.load_current_resource + end + end + + it "sets the attributes of current_resource" do + allow(provider).to receive(:load_task_hash).and_return(task_hash) + current_resource = provider.load_current_resource + expect(current_resource.exists).to be(true) + expect(current_resource.command).to eq("chef-client") + expect(current_resource.user).to eq("SYSTEM") + expect(current_resource.run_level).to eq(:highest) + expect(current_resource.frequency).to eq(:minute) + expect(current_resource.frequency_modifier).to eq(15) + expect(current_resource.execution_time_limit).to eq("PT72H") + expect(current_resource.enabled).to be(true) + end + end + + describe "#action_create" do + it "doesn't create the same task if it's already existing" do + allow(provider).to receive(:load_task_hash).and_return(task_hash) + provider.load_current_resource + allow(provider).to receive(:task_need_update?).and_return(false) + provider.run_action(:create) + expect(new_resource).not_to be_updated_by_last_action + end + + context "when task is not existing" do + before do + allow(provider).to receive(:load_task_hash) + provider.load_current_resource + end + + it "creates the task if it's not already existing" do + allow(provider).to receive(:task_need_update?).and_return(true) + expect(provider).to receive(:run_schtasks).with("CREATE", { "F" => "", "SC" => :hourly, "MO" => 1, "TR" => nil, "RU" => "SYSTEM" }) + provider.run_action(:create) + expect(new_resource).to be_updated_by_last_action + end + + it "updates the task XML if random_delay is provided" do + new_resource.random_delay "20" + allow(provider).to receive(:task_need_update?).and_return(true) + expect(provider).to receive(:run_schtasks).with("CREATE", { "F" => "", "SC" => :hourly, "MO" => 1, "TR" => nil, "RU" => "SYSTEM" }) + expect(provider).to receive(:update_task_xml) + provider.run_action(:create) + expect(new_resource).to be_updated_by_last_action + end + + it "updates the task XML if execution_time_limit is provided" do + new_resource.execution_time_limit "20" + allow(provider).to receive(:task_need_update?).and_return(true) + expect(provider).to receive(:run_schtasks).with("CREATE", { "F" => "", "SC" => :hourly, "MO" => 1, "TR" => nil, "RU" => "SYSTEM" }) + expect(provider).to receive(:update_task_xml) + provider.run_action(:create) + expect(new_resource).to be_updated_by_last_action + end + end + end + + describe "#action_run" do + it "does nothing if the task doesn't exist" do + allow(provider).to receive(:load_task_hash) + provider.load_current_resource + provider.run_action(:run) + expect(new_resource).not_to be_updated_by_last_action + end + + context "when the task exists" do + it "does nothing if the task is already running" do + task_hash[:Status] = "Running" + allow(provider).to receive(:load_task_hash).and_return(task_hash) + provider.load_current_resource + provider.run_action(:run) + expect(new_resource).not_to be_updated_by_last_action + end + + it "runs the task" do + allow(provider).to receive(:load_task_hash).and_return(task_hash) + provider.load_current_resource + expect(provider).to receive(:run_schtasks).with("RUN") + provider.run_action(:run) + expect(new_resource).to be_updated_by_last_action + end + end + end + + describe "#action_delete" do + it "deletes the task if it exists" do + allow(provider).to receive(:load_task_hash).and_return(task_hash) + provider.load_current_resource + expect(provider).to receive(:run_schtasks).with("DELETE", { "F" => "" }) + provider.run_action(:delete) + expect(new_resource).to be_updated_by_last_action + end + + it "does nothing if the task doesn't exist" do + allow(provider).to receive(:load_task_hash) + provider.load_current_resource + provider.run_action(:delete) + expect(new_resource).not_to be_updated_by_last_action + end + end + + describe "#action_end" do + it "does nothing if the task doesn't exist" do + allow(provider).to receive(:load_task_hash) + provider.load_current_resource + provider.run_action(:end) + expect(new_resource).not_to be_updated_by_last_action + end + + context "when the task exists" do + it "does nothing if the task is not running" do + allow(provider).to receive(:load_task_hash).and_return(task_hash) + provider.load_current_resource + provider.run_action(:end) + expect(new_resource).not_to be_updated_by_last_action + end + + it "ends the task if it's running" do + task_hash[:Status] = "Running" + allow(provider).to receive(:load_task_hash).and_return(task_hash) + provider.load_current_resource + expect(provider).to receive(:run_schtasks).with("END") + provider.run_action(:end) + expect(new_resource).to be_updated_by_last_action + end + end + end + + describe "#action_enable" do + it "raises error if the task doesn't exist" do + allow(provider).to receive(:load_task_hash) + provider.load_current_resource + expect { provider.run_action(:enable) }.to raise_error(Errno::ENOENT) + end + + context "when the task exists" do + it "does nothing if the task is already enabled" do + allow(provider).to receive(:load_task_hash).and_return(task_hash) + provider.load_current_resource + provider.run_action(:enable) + expect(new_resource).not_to be_updated_by_last_action + end + + it "enables the task if it exists" do + task_hash[:ScheduledTaskState] = "Disabled" + allow(provider).to receive(:load_task_hash).and_return(task_hash) + provider.load_current_resource + expect(provider).to receive(:run_schtasks).with("CHANGE", { "ENABLE" => "" }) + provider.run_action(:enable) + expect(new_resource).to be_updated_by_last_action + end + end + end + + describe "#action_disable" do + it "does nothing if the task doesn't exist" do + allow(provider).to receive(:load_task_hash) + provider.load_current_resource + provider.run_action(:disable) + expect(new_resource).not_to be_updated_by_last_action + end + + context "when the task exists" do + it "disables the task if it's enabled" do + allow(provider).to receive(:load_task_hash).and_return(task_hash) + provider.load_current_resource + expect(provider).to receive(:run_schtasks).with("CHANGE", { "DISABLE" => "" }) + provider.run_action(:disable) + expect(new_resource).to be_updated_by_last_action + end + + it "does nothing if the task is already disabled" do + task_hash[:ScheduledTaskState] = "Disabled" + allow(provider).to receive(:load_task_hash).and_return(task_hash) + provider.load_current_resource + provider.run_action(:disable) + expect(new_resource).not_to be_updated_by_last_action + end + end + end + + describe "#run_schtasks" do + before do + @task_action = "CREATE" + @options = { "F" => "", "SC" => :minute, "MO" => 15, "TR" => "chef-client", "RU" => "SYSTEM", "RL" => "HIGHEST" } + @cmd = "schtasks /CREATE /TN \"sample_task\" /F /SC \"minute\" /MO \"15\" /TR \"chef-client\" /RU \"SYSTEM\" /RL \"HIGHEST\" " + end + + it "forms the command properly from the given options" do + expect(provider).to receive(:shell_out!).with(@cmd, { :returns => [0] }) + provider.send(:run_schtasks, @task_action, @options) + end + end + + describe "#task_need_update?" do + context "when task doesn't exist" do + before do + allow(provider).to receive(:load_task_hash) + provider.load_current_resource + end + + it "returns true" do + new_resource.command "chef-client" + expect(provider.send(:task_need_update?)).to be(true) + end + end + + context "when the task exists" do + before do + allow(provider).to receive(:load_task_hash).and_return(task_hash) + provider.load_current_resource + + new_resource.command "chef-client" + new_resource.run_level :highest + new_resource.frequency :minute + new_resource.frequency_modifier 15 + new_resource.user "SYSTEM" + new_resource.execution_time_limit "PT72H" + end + + context "when no attributes are modified" do + it "returns false" do + expect(provider.send(:task_need_update?)).to be(false) + end + end + + context "when frequency_modifier is updated" do + it "returns true" do + new_resource.frequency_modifier 25 + expect(provider.send(:task_need_update?)).to be(true) + end + end + + context "when months are updated" do + it "returns true" do + new_resource.months "JAN" + expect(provider.send(:task_need_update?)).to be(true) + end + end + end + end + + describe "#update_task_xml" do + before do + new_resource.command "chef-client" + new_resource.run_level :highest + new_resource.frequency :minute + new_resource.frequency_modifier 15 + new_resource.user "SYSTEM" + new_resource.random_delay "20" + end + + it "does nothing if the task doesn't exist" do + task_xml = double("xml", :exitstatus => 1) + allow(provider).to receive(:powershell_out).and_return(task_xml) + output = provider.send(:update_task_xml, ["random_delay"]) + expect(output).to be(nil) + end + + it "updates the task XML if random_delay is passed" do + shell_out_obj = double("xml", :exitstatus => 0, :stdout => task_xml) + allow(provider).to receive(:powershell_out).and_return(shell_out_obj) + expect(::File).to receive(:join) + expect(::File).to receive(:open) + expect(::File).to receive(:delete) + expect(provider).to receive(:run_schtasks).twice + output = provider.send(:update_task_xml, ["random_delay"]) + end + end + + describe "#load_task_hash" do + it "returns false if the task doesn't exist" do + allow(provider).to receive_message_chain(:powershell_out, :stdout, :force_encoding).and_return("") + allow(provider).to receive(:load_task_xml) + expect(provider.send(:load_task_hash, "chef-client")).to be(false) + end + + it "returns task hash if the task exists" do + powershell_output = "\r\nFolder: \\\r\nHostName: NIMISHA-PC\r\nTaskName: \\chef-client\r\n" + task_h = { :"" => "", :Folder => "\\", :HostName => "NIMISHA-PC", :TaskName => "\\chef-client" } + allow(provider).to receive_message_chain(:powershell_out, :stdout, :force_encoding).and_return(powershell_output) + allow(provider).to receive(:load_task_xml).with("chef-client") + expect(provider.send(:load_task_hash, "chef-client")).to eq(task_h) + end + end + + describe "#frequency_modifier_allowed" do + it "returns true for frequency :hourly" do + new_resource.frequency :hourly + expect(provider.send(:frequency_modifier_allowed)).to be(true) + end + + it "returns true for frequency :monthly if frequency_modifier is THIRD" do + new_resource.frequency :monthly + new_resource.frequency_modifier "THIRD" + expect(provider.send(:frequency_modifier_allowed)).to be(true) + end + + it "returns false for frequency :once" do + new_resource.frequency :once + expect(provider.send(:frequency_modifier_allowed)).to be(false) + end + end +end diff --git a/spec/unit/provider_spec.rb b/spec/unit/provider_spec.rb index f252d3177d..6eb2bb9303 100644 --- a/spec/unit/provider_spec.rb +++ b/spec/unit/provider_spec.rb @@ -195,4 +195,11 @@ describe Chef::Provider do end end + context "when using use_inline_resources" do + it "should log a deprecation warning" do + pending Chef::VERSION.start_with?("13.6") + expect(Chef).to receive(:deprecated).with(:use_inline_resources, kind_of(String)) + Class.new(described_class) { use_inline_resources } + end + end end diff --git a/spec/unit/resource/conditional_spec.rb b/spec/unit/resource/conditional_spec.rb index 135f798676..0219945936 100644 --- a/spec/unit/resource/conditional_spec.rb +++ b/spec/unit/resource/conditional_spec.rb @@ -92,7 +92,7 @@ describe Chef::Resource::Conditional do describe "after running a command which timed out" do before do @conditional = Chef::Resource::Conditional.only_if(@parent_resource, "false") - allow_any_instance_of(Chef::GuardInterpreter::DefaultGuardInterpreter).to receive(:shell_out).and_raise(Chef::Exceptions::CommandTimeout) + allow_any_instance_of(Chef::GuardInterpreter::DefaultGuardInterpreter).to receive(:shell_out_with_systems_locale).and_raise(Chef::Exceptions::CommandTimeout) end it "indicates that resource convergence should not continue" do @@ -195,7 +195,7 @@ describe Chef::Resource::Conditional do describe "after running a command which timed out" do before do @conditional = Chef::Resource::Conditional.not_if(@parent_resource, "false") - allow_any_instance_of(Chef::GuardInterpreter::DefaultGuardInterpreter).to receive(:shell_out).and_raise(Chef::Exceptions::CommandTimeout) + allow_any_instance_of(Chef::GuardInterpreter::DefaultGuardInterpreter).to receive(:shell_out_with_systems_locale).and_raise(Chef::Exceptions::CommandTimeout) end it "indicates that resource convergence should continue" do diff --git a/spec/unit/resource/package_spec.rb b/spec/unit/resource/package_spec.rb index 8c00ea2bdd..84f92f26b5 100644 --- a/spec/unit/resource/package_spec.rb +++ b/spec/unit/resource/package_spec.rb @@ -61,7 +61,12 @@ describe Chef::Resource::Package do it "should accept a string for the options" do @resource.options "something" - expect(@resource.options).to eql("something") + expect(@resource.options).to eql(["something"]) + end + + it "should split options" do + @resource.options "-a -b 'arg with spaces' -b \"and quotes\"" + expect(@resource.options).to eql(["-a", "-b", "arg with spaces", "-b", "and quotes"]) end describe "when it has a package_name and version" do @@ -74,7 +79,7 @@ describe Chef::Resource::Package do it "describes its state" do state = @resource.state_for_resource_reporter expect(state[:version]).to eq("10.9.8") - expect(state[:options]).to eq("-al") + expect(state[:options]).to eq(["-al"]) end it "returns the file path as its identity" do diff --git a/spec/unit/resource/windows_task_spec.rb b/spec/unit/resource/windows_task_spec.rb new file mode 100644 index 0000000000..fa2d458bbb --- /dev/null +++ b/spec/unit/resource/windows_task_spec.rb @@ -0,0 +1,213 @@ +# +# Author:: Nimisha Sharad (<nimisha.sharad@msystechnologies.com>) +# Copyright:: Copyright 2008-2017, 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::WindowsTask do + let(:resource) { Chef::Resource::WindowsTask.new("sample_task") } + + it "creates a new Chef::Resource::WindowsTask" do + expect(resource).to be_a_kind_of(Chef::Resource) + expect(resource).to be_a_instance_of(Chef::Resource::WindowsTask) + end + + it "sets resource name as :windows_task" do + expect(resource.resource_name).to eql(:windows_task) + end + + it "sets the task_name as it's name" do + expect(resource.task_name).to eql("sample_task") + end + + it "sets the default action as :create" do + expect(resource.action).to eql(:create) + end + + it "sets the default user as System" do + expect(resource.user).to eql("SYSTEM") + end + + it "sets the default run_level as :limited" do + expect(resource.run_level).to eql(:limited) + end + + it "sets the default force as false" do + expect(resource.force).to eql(false) + end + + it "sets the default interactive_enabled as false" do + expect(resource.interactive_enabled).to eql(false) + end + + it "sets the default frequency_modifier as 1" do + expect(resource.frequency_modifier).to eql(1) + end + + it "sets the default frequency as :hourly" do + expect(resource.frequency).to eql(:hourly) + end + + context "when random_delay is passed" do + it "raises error if frequency is `:once`" do + resource.frequency :once + resource.random_delay "20" + expect { resource.after_created }.to raise_error(Chef::Exceptions::ArgumentError, "`random_delay` property is supported only for frequency :minute, :hourly, :daily, :weekly and :monthly") + end + + it "raises error for invalid random_delay" do + resource.frequency :monthly + resource.random_delay "xyz" + expect { resource.after_created }.to raise_error(Chef::Exceptions::ArgumentError, "Invalid value passed for `random_delay`. Please pass seconds as a String e.g. '60'.") + end + + it "converts seconds into iso8601 format" do + resource.frequency :monthly + resource.random_delay "60" + resource.after_created + expect(resource.random_delay).to eq("PT60S") + end + end + + context "when execution_time_limit is passed" do + it "sets the deafult value as PT72H" do + resource.after_created + expect(resource.execution_time_limit).to eq("PT72H") + end + + it "raises error for invalid execution_time_limit" do + resource.execution_time_limit "abc" + expect { resource.after_created }.to raise_error(Chef::Exceptions::ArgumentError, "Invalid value passed for `execution_time_limit`. Please pass seconds as a String e.g. '60'.") + end + + it "converts seconds into iso8601 format" do + resource.execution_time_limit "60" + resource.after_created + expect(resource.execution_time_limit).to eq("PT60S") + end + end + + context "#validate_start_time" do + it "raises error if start_time is nil" do + expect { resource.send(:validate_start_time, nil) }.to raise_error(Chef::Exceptions::ArgumentError, "`start_time` needs to be provided with `frequency :once`") + end + end + + context "#validate_start_day" do + it "raise error if start_day is passed with frequency :on_logon" do + resource.frequency :on_logon + expect { resource.send(:validate_start_day, "Wed", :on_logon) }.to raise_error(Chef::Exceptions::ArgumentError, "`start_day` property is not supported with frequency: on_logon") + end + end + + context "#validate_user_and_password" do + context "when password is not passed" do + it "raises error with non-system users" do + allow(resource).to receive(:use_password?).and_return(true) + expect { resource.send(:validate_user_and_password, "Testuser", nil) }.to raise_error("Can't specify a non-system user without a password!") + end + end + end + + context "#validate_interactive_setting" do + it "raises error when interactive_enabled is passed without password" do + expect { resource.send(:validate_interactive_setting, true, nil) }.to raise_error("Please provide the password when attempting to set interactive/non-interactive.") + end + end + + context "#validate_create_frequency_modifier" do + context "when frequency is :minute" do + it "raises error if frequency_modifier > 1439" do + expect { resource.send(:validate_create_frequency_modifier, :minute, 1500) }.to raise_error("frequency_modifier value 1500 is invalid. Valid values for :minute frequency are 1 - 1439.") + end + end + + context "when frequency is :hourly" do + it "raises error if frequency_modifier > 23" do + expect { resource.send(:validate_create_frequency_modifier, :hourly, 24) }.to raise_error("frequency_modifier value 24 is invalid. Valid values for :hourly frequency are 1 - 23.") + end + end + + context "when frequency is :daily" do + it "raises error if frequency_modifier > 365" do + expect { resource.send(:validate_create_frequency_modifier, :daily, 366) }.to raise_error("frequency_modifier value 366 is invalid. Valid values for :daily frequency are 1 - 365.") + end + end + + context "when frequency is :weekly" do + it "raises error if frequency_modifier > 52" do + expect { resource.send(:validate_create_frequency_modifier, :weekly, 53) }.to raise_error("frequency_modifier value 53 is invalid. Valid values for :weekly frequency are 1 - 52.") + end + end + + context "when frequency is :monthly" do + it "raises error if frequency_modifier > 12" do + expect { resource.send(:validate_create_frequency_modifier, :monthly, 14) }.to raise_error("frequency_modifier value 14 is invalid. Valid values for :monthly frequency are 1 - 12, 'FIRST', 'SECOND', 'THIRD', 'FOURTH', 'LAST', 'LASTDAY'.") + end + + it "raises error if frequency_modifier is invalid" do + expect { resource.send(:validate_create_frequency_modifier, :monthly, "abc") }.to raise_error("frequency_modifier value abc is invalid. Valid values for :monthly frequency are 1 - 12, 'FIRST', 'SECOND', 'THIRD', 'FOURTH', 'LAST', 'LASTDAY'.") + end + end + end + + context "#validate_create_day" do + it "raises error if frequency is not :weekly" do + expect { resource.send(:validate_create_day, "Mon", :monthly) }.to raise_error("day attribute is only valid for tasks that run weekly") + end + + it "accepts a valid single day" do + expect { resource.send(:validate_create_day, "Mon", :weekly) }.not_to raise_error + end + + it "accepts a comma separated list of valid days" do + expect { resource.send(:validate_create_day, "Mon, tue, THU", :weekly) }.not_to raise_error + end + + it "raises error for invalid day value" do + expect { resource.send(:validate_create_day, "xyz", :weekly) }.to raise_error("day attribute invalid. Only valid values are: MON, TUE, WED, THU, FRI, SAT, SUN and *. Multiple values must be separated by a comma.") + end + end + + context "#validate_create_months" do + it "raises error if frequency is not :monthly" do + expect { resource.send(:validate_create_months, "Jan", :once) }.to raise_error("months attribute is only valid for tasks that run monthly") + end + + it "accepts a valid single month" do + expect { resource.send(:validate_create_months, "Feb", :monthly) }.not_to raise_error + end + + it "accepts a comma separated list of valid months" do + expect { resource.send(:validate_create_months, "Jan, mar, AUG", :monthly) }.not_to raise_error + end + + it "raises error for invalid month value" do + expect { resource.send(:validate_create_months, "xyz", :monthly) }.to raise_error("months attribute invalid. Only valid values are: JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC and *. Multiple values must be separated by a comma.") + end + end + + context "#validate_idle_time" do + it "raises error if frequency is not :on_idle" do + expect { resource.send(:validate_idle_time, 5, :hourly) }.to raise_error("idle_time attribute is only valid for tasks that run on_idle") + end + + it "raises error if idle_time > 999" do + expect { resource.send(:validate_idle_time, 1000, :on_idle) }.to raise_error("idle_time value 1000 is invalid. Valid values for :on_idle frequency are 1 - 999.") + end + end +end diff --git a/spec/unit/resource_collection/resource_set_spec.rb b/spec/unit/resource_collection/resource_set_spec.rb index ecf3e2707f..9955bc2bea 100644 --- a/spec/unit/resource_collection/resource_set_spec.rb +++ b/spec/unit/resource_collection/resource_set_spec.rb @@ -27,6 +27,7 @@ describe Chef::ResourceCollection::ResourceSet do let(:zen_master) { Chef::Resource::ZenMaster.new(zen_master_name) } let(:zen_master2) { Chef::Resource::ZenMaster.new(zen_master2_name) } let(:zen_follower) { Chef::Resource::ZenFollower.new(zen_follower_name) } + let(:zen_array) { Chef::Resource::ZenMaster.new( [ zen_master_name, zen_master2_name ]) } describe "initialize" do it "should return a Chef::ResourceSet" do @@ -113,13 +114,27 @@ describe Chef::ResourceCollection::ResourceSet do end it "should find resources by strings of zen_master[a,b]" do + Chef::Config[:treat_deprecation_warnings_as_errors] = false collection.insert_as(zen_master) collection.insert_as(zen_master2) check_by_names(collection.find("zen_master[#{zen_master_name},#{zen_master2_name}]"), zen_master_name, zen_master2_name) end + it "should find array names" do + collection.insert_as(zen_array) + expect(collection.find("zen_master[#{zen_master_name}, #{zen_master2_name}]")).to eql(zen_array) + end + + it "should favor array names over multi resource syntax" do + collection.insert_as(zen_master) + collection.insert_as(zen_master2) + collection.insert_as(zen_array) + expect(collection.find("zen_master[#{zen_master_name}, #{zen_master2_name}]")).to eql(zen_array) + end + it "should find resources by strings of zen_master[a,b] with custom names" do + Chef::Config[:treat_deprecation_warnings_as_errors] = false collection.insert_as(zen_master, :zzz, "name1") collection.insert_as(zen_master2, :zzz, "name2") check_by_names(collection.find("zzz[name1,name2]"), @@ -134,6 +149,7 @@ describe Chef::ResourceCollection::ResourceSet do end it "should find resources of multiple types by strings of zen_master[a] with custom names" do + Chef::Config[:treat_deprecation_warnings_as_errors] = false collection.insert_as(zen_master, :zzz, "name1") collection.insert_as(zen_master2, :zzz, "name2") collection.insert_as(zen_follower, :yyy, "name3") diff --git a/spec/unit/resource_collection_spec.rb b/spec/unit/resource_collection_spec.rb index 76038e51b9..c696572b13 100644 --- a/spec/unit/resource_collection_spec.rb +++ b/spec/unit/resource_collection_spec.rb @@ -1,7 +1,7 @@ # # Author:: Adam Jacob (<adam@chef.io>) # Author:: Christopher Walters (<cw@chef.io>) -# Copyright:: Copyright 2008-2016, Chef Software Inc. +# Copyright:: Copyright 2008-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -219,6 +219,7 @@ describe Chef::ResourceCollection do end it "should find resources by strings of zen_master[a,b]" do + Chef::Config[:treat_deprecation_warnings_as_errors] = false load_up_resources results = rc.resources("zen_master[monkey,dog]") expect(results.length).to eql(2) diff --git a/spec/unit/run_context/cookbook_compiler_spec.rb b/spec/unit/run_context/cookbook_compiler_spec.rb index feb39615b6..e93088cd5f 100644 --- a/spec/unit/run_context/cookbook_compiler_spec.rb +++ b/spec/unit/run_context/cookbook_compiler_spec.rb @@ -163,9 +163,7 @@ describe Chef::RunContext::CookbookCompiler do describe "event dispatch" do let(:recipe) { "dependency1::default" } let(:recipe_path) do - File.expand_path("../../../data/run_context/cookbooks/dependency1/recipes/default.rb", __FILE__).tap do |path| - path.gsub!(File::SEPARATOR, File::ALT_SEPARATOR) if File::ALT_SEPARATOR - end + File.expand_path("../../../data/run_context/cookbooks/dependency1/recipes/default.rb", __FILE__) end before do node.run_list(recipe) diff --git a/spec/unit/search/query_spec.rb b/spec/unit/search/query_spec.rb index 0837410b3c..95221870d5 100644 --- a/spec/unit/search/query_spec.rb +++ b/spec/unit/search/query_spec.rb @@ -1,6 +1,6 @@ # # Author:: Adam Jacob (<adam@chef.io>) -# Copyright:: Copyright 2009-2016, Chef Software Inc. +# Copyright:: Copyright 2009-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -237,6 +237,27 @@ describe Chef::Search::Query do end end + it "fuzzifies node searches when fuzz is set" do + expect(rest).to receive(:get).with( + "search/node?q=tags:*free.messi*%20OR%20roles:*free.messi*%20OR%20fqdn:*free.messi*%20OR%20addresses:*free.messi*%20OR%20policy_name:*free.messi*%20OR%20policy_group:*free.messi*&start=0" + ).and_return(response) + query.search(:node, "free.messi", fuzz: true) + end + + it "does not fuzzify node searches when fuzz is not set" do + expect(rest).to receive(:get).with( + "search/node?q=free.messi&start=0" + ).and_return(response) + query.search(:node, "free.messi") + end + + it "does not fuzzify client searches" do + expect(rest).to receive(:get).with( + "search/client?q=messi&start=0" + ).and_return(response) + query.search(:client, "messi", fuzz: true) + end + context "when :filter_result is provided as a result" do include_context "filtered search" do let(:filter_key) { :filter_result } diff --git a/tasks/bin/bundle-platform b/tasks/bin/bundle-platform deleted file mode 100755 index aaf433c98d..0000000000 --- a/tasks/bin/bundle-platform +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env ruby -require_relative "bundler_patch" - -platforms = ARGV.shift -platforms = platforms.split(" ").map { |p| Gem::Platform.new(p) } -Gem::Platform.instance_eval { @local = platforms.last } -old_platforms = Gem.platforms -Gem.platforms = platforms -puts "bundle-platform set Gem.platforms to #{Gem.platforms.map { |p| p.to_s }} (was #{old_platforms.map { |p| p.to_s }})" - -desired_version = ARGV.shift.delete("_") - -# The rest of this is a normal bundler binstub -require "pathname" -ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", - Pathname.new(__FILE__).realpath) - -require "rubygems" - -load Gem.bin_path("bundler", "bundle", desired_version) diff --git a/tasks/bin/bundle-platform.bat b/tasks/bin/bundle-platform.bat deleted file mode 100644 index d193eb0c05..0000000000 --- a/tasks/bin/bundle-platform.bat +++ /dev/null @@ -1,2 +0,0 @@ -@ECHO OFF -ruby "%~dpn0" %* diff --git a/tasks/bin/bundler_patch.rb b/tasks/bin/bundler_patch.rb deleted file mode 100644 index 5665e44eca..0000000000 --- a/tasks/bin/bundler_patch.rb +++ /dev/null @@ -1,27 +0,0 @@ -# This is a temporary monkey patch to address https://github.com/bundler/bundler/issues/4896 -# the heart of the fix is line #18 with the addition of: -# && (possibility.activated - existing_node.payload.activated).empty? -# This ensures we do not mis linux platform gems in some scenarios like ffi in kitchen-test. -# There is a permanent fix to bundler (See https://github.com/bundler/bundler/pull/4836) which -# is due to ship in v1.14. Once we adopt that version, we can remove this file - -require "bundler" -require "bundler/vendor/molinillo/lib/molinillo/resolution" - -module Bundler::Molinillo - class Resolver - # A specific resolution from a given {Resolver} - class Resolution - def attempt_to_activate - debug(depth) { "Attempting to activate " + possibility.to_s } - existing_node = activated.vertex_named(name) - if existing_node.payload && (possibility.activated - existing_node.payload.activated).empty? - debug(depth) { "Found existing spec (#{existing_node.payload})" } - attempt_to_activate_existing_spec(existing_node) - else - attempt_to_activate_new_spec - end - end - end - end -end diff --git a/tasks/bin/gem-version-diff b/tasks/bin/gem-version-diff deleted file mode 100755 index 617402d4e6..0000000000 --- a/tasks/bin/gem-version-diff +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env ruby - -require_relative "../../version_policy" - -old_version, new_version = ARGV[0..1] - -require "set" -ENV["BUNDLE_WITHOUT"] = INSTALL_WITHOUT_GROUPS.join(":") -relevant_gems = Set.new -`bundle list`.each_line do |line| - next unless line =~ /^ \* (\S+)/ - relevant_gems.add($1) -end - -old_gems = {} -old_file = `git show #{old_version}:Gemfile.lock` -old_file.each_line do |line| - next unless line =~ /^ (\S+) \(([^\)]+)\)$/ - next unless relevant_gems.include?($1) - old_gems[$1] = $2 -end - -new_gems = {} -new_file = `git show #{new_version}:Gemfile.lock` -new_file.each_line do |line| - next unless line =~ /^ (\S+) \(([^\)]+)\)$/ - next unless relevant_gems.include?($1) - new_gems[$1] = $2 -end - -modified_gems = (old_gems.keys & new_gems.keys).sort.select { |name| new_gems[name] != old_gems[name] }.map { |name| "#{name} - #{new_gems[name]} (was #{old_gems[name]})" } -removed_gems = (old_gems.keys - new_gems.keys).sort.map { |name| "#{name} - #{old_gems[name]}" } -added_gems = (new_gems.keys - old_gems.keys).sort.map { |name| "#{name} - #{new_gems[name]}" } - -puts "MODIFIED:\n#{modified_gems.join("\n")}" if modified_gems.any? -puts "\nADDED:\n#{added_gems.join("\n")}" if added_gems.any? -puts "\nREMOVED:\n#{removed_gems.join("\n")}" if removed_gems.any? diff --git a/tasks/bundle.rb b/tasks/bundle.rb index f530515786..0e9b207932 100644 --- a/tasks/bundle.rb +++ b/tasks/bundle.rb @@ -15,7 +15,6 @@ # limitations under the License. # -require_relative "bundle_util" require_relative "../version_policy" require "fileutils" @@ -23,33 +22,22 @@ desc "Tasks to work with the main Gemfile and Gemfile.<platform>" namespace :bundle do desc "Update Gemfile.lock and all Gemfile.<platform>.locks (or one or more gems via bundle:update gem1 gem2 ...)." task :update, [:args] do |t, rake_args| - extend BundleUtil args = rake_args[:args] || "" - with_bundle_unfrozen do - puts "" - puts "-------------------------------------------------------------------" - puts "Updating Gemfile.lock ..." - puts "-------------------------------------------------------------------" - bundle "update #{args}" - platforms.each do |platform| - bundle "update #{args}", platform: platform - end + Bundler.with_clean_env do + sh "bundle config --local frozen '0'" + sh "bundle update #{args}" + sh "bundle config --local frozen '1'" end end desc "Conservatively update Gemfile.lock and all Gemfile.<platform>.locks" task :install, [:args] do |t, rake_args| - extend BundleUtil args = rake_args[:args] || "" - with_bundle_unfrozen do - puts "" - puts "-------------------------------------------------------------------" - puts "Updating Gemfile.lock ..." - puts "-------------------------------------------------------------------" - bundle "install #{args}" - platforms.each do |platform| - bundle "lock", platform: platform - end + args = rake_args[:args] || "" + Bundler.with_clean_env do + sh "bundle config --local frozen '0'" + sh "bundle install #{args}" + sh "bundle config --local frozen '1'" end end @@ -82,20 +70,12 @@ namespace :bundle do end end -desc "Run bundle with arbitrary args against the given platform; e.g. rake bundle[show]. No platform to run against the main bundle; bundle[show,windows] to run the windows one; bundle[show,*] to run against all non-default platforms." -task :bundle, [:args, :platform] do |t, rake_args| - extend BundleUtil +desc "Run bundle with arbitrary args" +task :bundle, [:args] do |t, rake_args| args = rake_args[:args] || "" - platform = rake_args[:platform] - with_bundle_unfrozen do - if platform == "*" - platforms.each do |platform| - bundle args, platform: platform - end - elsif platform - bundle args, platform: platform - else - bundle args - end + Bundler.with_clean_env do + sh "bundle config --local frozen '0'" + sh "bundle #{args}" + sh "bundle config --local frozen '1'" end end diff --git a/tasks/bundle_util.rb b/tasks/bundle_util.rb deleted file mode 100644 index 67647dd4f0..0000000000 --- a/tasks/bundle_util.rb +++ /dev/null @@ -1,110 +0,0 @@ -require "bundler" -require "shellwords" - -module BundleUtil - PLATFORMS = { "windows" => %w{ruby x86-mingw32} } - - def project_root - File.expand_path("../..", __FILE__) - end - - def bundle_platform - File.join(project_root, "tasks", "bin", "bundle-platform") - end - - # Parse the output of "bundle outdated" and get the list of gems that - # were outdated - def parse_bundle_outdated(bundle_outdated_output) - result = [] - bundle_outdated_output.each_line do |line| - if line =~ /^\s*\* (.+) \(newest ([^,]+), installed ([^,)])*/ - gem_name, newest_version, installed_version = $1, $2, $3 - result << [ line, gem_name ] - end - end - result - end - - def with_bundle_unfrozen(cwd: nil, leave_frozen: false) - bundle "config --delete frozen", cwd: cwd - begin - yield - ensure - bundle "config --local frozen 1", cwd: cwd unless leave_frozen - end - end - - # Run bundle-platform with the given ruby platform(s) - def bundle(args, gemfile: nil, platform: nil, cwd: nil, extract_output: false, delete_gemfile_lock: false) - args = args.split(/\s+/) - if cwd - prefix = "[#{cwd}] " - end - cwd = File.expand_path(cwd || ".", project_root) - Bundler.with_clean_env do - Dir.chdir(cwd) do - gemfile ||= "Gemfile" - gemfile = File.expand_path(gemfile, cwd) - raise "No platform #{platform} (supported: #{PLATFORMS.keys.join(", ")})" if platform && !PLATFORMS[platform] - - # First delete the gemfile.lock - if delete_gemfile_lock - if File.exist?("#{gemfile}.lock") - puts "Deleting #{gemfile}.lock ..." - File.delete("#{gemfile}.lock") - end - end - - # Run the bundle command - ruby_platforms = platform ? PLATFORMS[platform].join(" ") : "ruby" - cmd = Shellwords.join([ - Gem.ruby, - "-S", - bundle_platform, - ruby_platforms, - "_#{desired_bundler_version}_", - *args, - ]) - puts "#{prefix}#{Shellwords.join(["bundle", *args])}#{platform ? " for #{platform} platform" : ""}:" - with_gemfile(gemfile) do - puts "#{prefix}BUNDLE_GEMFILE=#{gemfile}" - puts "#{prefix}> #{cmd}" - if extract_output - `#{cmd}` - else - unless system(bundle_platform, ruby_platforms, "_#{desired_bundler_version}_", *args) - raise "#{bundle_platform} failed: exit code #{$?}" - end - end - end - end - end - end - - def with_gemfile(gemfile) - old_gemfile = ENV["BUNDLE_GEMFILE"] - ENV["BUNDLE_GEMFILE"] = gemfile - begin - yield - ensure - if old_gemfile - ENV["BUNDLE_GEMFILE"] = old_gemfile - else - ENV.delete("BUNDLE_GEMFILE") - end - end - end - - def platforms - PLATFORMS.keys - end - - def desired_bundler_version - @desired_bundler_version ||= begin - omnibus_overrides = File.join(project_root, "omnibus_overrides.rb") - File.readlines(omnibus_overrides).each do |line| - return $1 if line =~ /^override :bundler, version: "(.+)"$/ - end - end - end -end diff --git a/tasks/dependencies.rb b/tasks/dependencies.rb index 6b836b747e..2118644b12 100644 --- a/tasks/dependencies.rb +++ b/tasks/dependencies.rb @@ -15,7 +15,6 @@ # limitations under the License. # -require_relative "bundle_util" require_relative "bundle" require_relative "../version_policy" |