summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.mailmap65
-rw-r--r--.travis.yml199
-rw-r--r--CHANGELOG.md36
-rw-r--r--DOC_CHANGES.md20
-rw-r--r--Gemfile19
-rw-r--r--Gemfile.lock61
-rw-r--r--MAINTAINERS.md10
-rw-r--r--MAINTAINERS.toml2
-rw-r--r--NOTICE2
-rw-r--r--RELEASE_NOTES.md55
-rw-r--r--ROADMAP.md4
-rw-r--r--VERSION2
-rw-r--r--acceptance/Gemfile.lock41
-rw-r--r--acceptance/data-collector/.acceptance/acceptance-cookbook/.gitignore2
-rw-r--r--acceptance/data-collector/.acceptance/acceptance-cookbook/metadata.rb3
-rw-r--r--acceptance/data-collector/.acceptance/acceptance-cookbook/recipes/destroy.rb2
-rw-r--r--acceptance/data-collector/.acceptance/acceptance-cookbook/recipes/provision.rb2
-rw-r--r--acceptance/data-collector/.acceptance/acceptance-cookbook/recipes/verify.rb2
-rw-r--r--acceptance/data-collector/.acceptance/data-collector-test/.gitignore16
-rw-r--r--acceptance/data-collector/.acceptance/data-collector-test/Berksfile3
-rw-r--r--acceptance/data-collector/.acceptance/data-collector-test/files/default/api.rb85
-rw-r--r--acceptance/data-collector/.acceptance/data-collector-test/files/default/apigemfile3
-rw-r--r--acceptance/data-collector/.acceptance/data-collector-test/files/default/client-rb-both-mode.rb4
-rw-r--r--acceptance/data-collector/.acceptance/data-collector-test/files/default/client-rb-client-mode.rb4
-rw-r--r--acceptance/data-collector/.acceptance/data-collector-test/files/default/client-rb-no-endpoint.rb2
-rw-r--r--acceptance/data-collector/.acceptance/data-collector-test/files/default/client-rb-solo-mode.rb4
-rw-r--r--acceptance/data-collector/.acceptance/data-collector-test/files/default/config.ru2
-rw-r--r--acceptance/data-collector/.acceptance/data-collector-test/metadata.rb7
-rw-r--r--acceptance/data-collector/.acceptance/data-collector-test/recipes/default.rb38
-rw-r--r--acceptance/data-collector/.kitchen.yml9
-rw-r--r--acceptance/data-collector/Berksfile3
-rw-r--r--acceptance/data-collector/Berksfile.lock6
-rw-r--r--acceptance/data-collector/test/integration/default/serverspec/default_spec.rb251
m---------acceptance/vendor/bundle/bundler/gems/chef-acceptance-47e931cec1000
m---------acceptance/vendor/bundle/bundler/gems/chef-acceptance-e92ddae46d210
-rw-r--r--chef-config/Rakefile12
-rw-r--r--chef-config/lib/chef-config/config.rb37
-rw-r--r--chef-config/lib/chef-config/version.rb2
-rwxr-xr-xci/bundle_install.sh9
-rwxr-xr-xci/dependency_update.sh9
-rwxr-xr-xci/version_bump.sh9
-rwxr-xr-xci/version_show.sh3
-rw-r--r--kitchen-tests/.kitchen.travis.yml112
-rw-r--r--kitchen-tests/.kitchen.yml13
-rw-r--r--kitchen-tests/Berksfile6
-rw-r--r--kitchen-tests/Berksfile.lock35
-rw-r--r--kitchen-tests/Gemfile17
-rw-r--r--kitchen-tests/Gemfile.lock120
-rw-r--r--kitchen-tests/cookbooks/base/attributes/default.rb11
-rw-r--r--kitchen-tests/cookbooks/base/libraries/chef-sugar.rb4
-rw-r--r--kitchen-tests/cookbooks/base/metadata.rb4
-rw-r--r--kitchen-tests/cookbooks/base/recipes/default.rb16
-rw-r--r--kitchen-tests/cookbooks/base/recipes/packages.rb2
-rw-r--r--kitchen-tests/test/integration/webapp/default_spec.rb118
-rw-r--r--kitchen-tests/test/integration/webapp/serverspec/Gemfile4
-rw-r--r--kitchen-tests/test/integration/webapp/serverspec/Gemfile.lock19
-rw-r--r--kitchen-tests/test/integration/webapp/serverspec/localhost/default_spec.rb127
m---------kitchen-tests/vendor/bundle/bundler/gems/kitchen-ec2-fec3f199a6460
-rw-r--r--lib/chef/application.rb23
-rw-r--r--lib/chef/application/apply.rb8
-rw-r--r--lib/chef/application/client.rb6
-rw-r--r--lib/chef/application/exit_code.rb226
-rw-r--r--lib/chef/application/solo.rb14
-rw-r--r--lib/chef/application/windows_service.rb6
-rw-r--r--lib/chef/client.rb8
-rw-r--r--lib/chef/config_fetcher.rb8
-rw-r--r--lib/chef/data_collector.rb333
-rw-r--r--lib/chef/data_collector/messages.rb125
-rw-r--r--lib/chef/data_collector/messages/helpers.rb161
-rw-r--r--lib/chef/data_collector/resource_report.rb84
-rw-r--r--lib/chef/dsl/platform_introspection.rb16
-rw-r--r--lib/chef/exceptions.rb23
-rw-r--r--lib/chef/platform/rebooter.rb15
-rw-r--r--lib/chef/provider/directory.rb4
-rw-r--r--lib/chef/provider/package/chocolatey.rb18
-rw-r--r--lib/chef/provider/package/portage.rb2
-rw-r--r--lib/chef/provider/package/yum.rb963
-rw-r--r--lib/chef/provider/package/yum/rpm_utils.rb642
-rw-r--r--lib/chef/provider/package/yum/yum-dump.py (renamed from lib/chef/provider/package/yum-dump.py)0
-rw-r--r--lib/chef/provider/package/yum/yum_cache.rb376
-rw-r--r--lib/chef/provider/systemd_unit.rb18
-rw-r--r--lib/chef/resource/file.rb7
-rw-r--r--lib/chef/resource/systemd_unit.rb4
-rw-r--r--lib/chef/version.rb2
-rw-r--r--omnibus/Gemfile.lock30
-rw-r--r--omnibus/config/software/chef-appbundle.rb3
-rw-r--r--omnibus_overrides.rb2
-rw-r--r--spec/functional/resource/chocolatey_package_spec.rb5
-rw-r--r--spec/functional/resource/dsc_script_spec.rb1
-rw-r--r--spec/integration/client/exit_code_spec.rb245
-rw-r--r--spec/unit/application/apply_spec.rb6
-rw-r--r--spec/unit/application/exit_code_spec.rb231
-rw-r--r--spec/unit/application/solo_spec.rb25
-rw-r--r--spec/unit/config_fetcher_spec.rb4
-rw-r--r--spec/unit/data_collector/messages/helpers_spec.rb190
-rw-r--r--spec/unit/data_collector/messages_spec.rb214
-rw-r--r--spec/unit/data_collector_spec.rb502
-rw-r--r--spec/unit/provider/directory_spec.rb12
-rw-r--r--spec/unit/provider/package/chocolatey_spec.rb16
-rw-r--r--spec/unit/provider/package/portage_spec.rb6
-rw-r--r--spec/unit/provider/package/yum/yum_cache_spec.rb27
-rw-r--r--spec/unit/provider/systemd_unit_spec.rb63
-rwxr-xr-xtasks/bin/bundle-platform8
-rw-r--r--tasks/bundle_util.rb20
m---------vendor/bundle/bundler/gems/bundler-audit-4e32fca89d750
m---------vendor/bundle/bundler/gems/chefstyle-52a0d55a9e8f0
106 files changed, 4892 insertions, 1463 deletions
diff --git a/.mailmap b/.mailmap
index a7b6b6b277..614100091a 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1,5 +1,6 @@
# Daniel DeLeo
Daniel DeLeo <dan@chef.io> Daniel DeLeo <dan@opscode.com>
+Daniel DeLeo <dan@chef.io> danielsdeleo <dan@chef.io>
Daniel DeLeo <dan@chef.io> Dan DeLeo <danielsdeleo@mac.com>
Daniel DeLeo <dan@chef.io> Dan DeLeo <dan@kallistec.com>
Daniel DeLeo <dan@chef.io> danielsdeleo <dan@getchef.com>
@@ -62,11 +63,11 @@ Nuo Yan <nuo@opscode.com> Nuo Yan <nuoyan@nuo-yans-macbook-pro.(none)>
Nuo Yan <nuo@opscode.com> Nuo Yan <nuoyan@nuo-yans-macbook-pro.local>
# Thom May
-Thom May <thom@clearairturbulence.org>
-Thom May <thom@clearairturbulence.org> Thom May <thom.may@betfair.com>
-Thom May <thom@clearairturbulence.org> Thom May <thom@digital-science.com>
-Thom May <thom@clearairturbulence.org> Thom May <thom@virelais.nyc.joostas.com>
-Thom May <thom@clearairturbulence.org> Thom May <tmay@expedia.com>
+Thom May <thom@chef.io> Thom May <thom@clearairturbulence.org>
+Thom May <thom@chef.io> Thom May <thom.may@betfair.com>
+Thom May <thom@chef.io> Thom May <thom@digital-science.com>
+Thom May <thom@chef.io> Thom May <thom@virelais.nyc.joostas.com>
+Thom May <thom@chef.io> Thom May <tmay@expedia.com>
# Stephen Delano
Stephen Delano <stephen@chef.io> Stephen Delano <stephen@opscode.com>
@@ -114,4 +115,56 @@ Marc Paradise <marc@chef.io> marc@opscode.com <marc@opscode.com>
Tyler Ball <tyleraball@gmail.com> tyler-ball <tyleraball@gmail.com>
# Steven Danna
-Steven Danna <steve@chef.io> Steven Danna <steve@opscode.com> \ No newline at end of file
+Steven Danna <steve@chef.io> Steven Danna <steve@opscode.com>
+
+# Salim Alam
+Salim Alam <salam@chef.io> chefsalim <salam@chef.io>
+
+# Isa Farnik
+Isa Farnik <isa@chef.io> curiositycasualty <isa@getchef.com>
+Isa Farnik <isa@chef.io> curiositycasualty <isa@opscode.com>
+
+# Paul Mooring
+Paul Mooring <paul@chef.io> Paul Mooring <paul@opscode.com>
+
+# Jeremiah Snapp
+Jeremiah Snapp <jeremiah@chef.io> Jeremiah Snapp <jeremiah@getchef.com>
+Jeremiah Snapp <jeremiah@chef.io> Jeremiah Snapp <jeremiah.snapp@opscode.com>
+Jeremiah Snapp <jeremiah@chef.io> Jeremiah Snapp <jeremiah@opscode.com>
+
+# Mark Myzk
+Mark Mzyk <mmzyk@chef.io> Mark Mzyk <mmzyk@opscode.com>
+Mark Mzyk <mmzyk@chef.io> mmzyk <mmzyk@opscode.com>
+Mark Mzyk <mmzyk@chef.io> Mark Mzyk <markmzyk@Marks-MacBook-Pro.local>
+
+# Chris Doherty
+Chris Doherty <cdoherty@chef.io> Chris Doherty <cdoherty@getchef.com>
+Chris Doherty <cdoherty@chef.io> Chris Doherty <cdoherty@ooyala.com>
+Chris Doherty <cdoherty@chef.io> Chris Doherty <randomcamel@users.noreply.github.com>
+Chris Doherty <cdoherty@chef.io> unknown <cdoherty@chef.io>
+
+# Christopher Webber
+Christopher Webber <cwebber@chef.io> Christopher Webber <cwebber@getchef.com>
+
+# Tyler Cloke
+Tyler Cloke <tyler@chef.io> tylercloke <tyler@opscode.com>
+Tyler Cloke <tyler@chef.io> tylercloke <tylercloke@gmail.com>
+Tyler Cloke <tyler@chef.io> Tyler Cloke <tylercloke@gmail.com>
+
+# Julian Dunn
+Julian C. Dunn <jdunn@chef.io> Julian C. Dunn <jdunn@getchef.com>
+Julian C. Dunn <jdunn@chef.io> Julian C. Dunn <jdunn@opscode.com>
+Julian C. Dunn <jdunn@chef.io> Julian C. Dunn <jdunn@aquezada.com>
+
+# Tom Duffield
+Tom Duffield <tom@chef.io> Tom Duffield <tom@getchef.com>
+Tom Duffield <tom@chef.io> Tom Duffield <tom@opscode.com>
+
+# Scott Hain
+Scott Hain <shain@chef.io> Scott Hain <shain@getchef.com>
+
+# Peter Burkholder
+Peter Burkholder <pburkholder@chef.io> Peter Burkholder <peterb@getchef.com>
+
+# JJ Ashgar
+JJ Asghar <jj@chef.io> JJ Asghar <jj@getchef.com>
diff --git a/.travis.yml b/.travis.yml
index 2416a064f3..b81c538737 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -110,109 +110,144 @@ matrix:
TEST_GEM: poise
script: tasks/bin/run_external_test $TEST_GEM rake spec
rvm: 2.2
- #
### START TEST KITCHEN ONLY ###
#
- rvm: 2.2
+ 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)
- - echo -n $DO_KEY_CHUNK_{0..30} >> ~/.ssh/id_aws.base64
- - cat ~/.ssh/id_aws.base64 | tr -d ' ' | base64 --decode > ~/.ssh/id_aws.pem
before_script:
+ - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER )
- cd kitchen-tests
script:
- - if [ "$TRAVIS_SECURE_ENV_VARS" = "true" ]; then bundle exec kitchen test ubuntu-1404; fi
+ - bundle exec kitchen test ubuntu-1204
after_failure:
- cat .kitchen/logs/kitchen.log
- after_script:
- - if [ "$TRAVIS_SECURE_ENV_VARS" = "true" ]; then bundle exec kitchen destroy ubuntu-1404; fi
env:
- - UBUNTU=1
+ - UBUNTU=12.04
- KITCHEN_YAML=.kitchen.travis.yml
- - EC2_SSH_KEY_PATH=~/.ssh/id_aws.pem
- - secure: VAauyVnAMWhqvnhJOJ/tCDn3XAdWqzbWiDVQPNBkqtm2SBIvhmZl2hlrusvw6YLU31Prdf8fSFhOSysVQQs/rJYrmD/1BfV79p6M7cGXYZ0nGWwldF81N296lyFoZLyrqtmG4G0cx3Pw2ojADFgFe+B5eTGlqJFD+z371g4RF/Y=
- - secure: A+qtUF2LPJGkUAdvt04AwZMt69rzaeTyR0/1XEOAuntBKKXSCzddUzr5ePDc9QQ/57AWywKxhVLpnxk3QzKN7r7zerDxyIJBgklNDpNAKkeQjP3T6FpaKEIN9ROcpPtsM6FJ5Agb+bEQoRJF7s+ampO3wLV3XpTiWNuWkcAhv9A=
- - secure: J8JIg15trrPgc8X/1DsaUWDQCdDWTvN/AorXzZ/ReudHS6G/KpoynZ5lTmKjlgFiFNE/TGMDv486pStGtIcarTKTuIEmNADdEWlAVH7bxclpayMjtppVuapRCkZWccs5gz5CJyhX7yhQCFTYoqVox9Y4qHGCluF3oqCcPRtCOOw=
- - secure: NJYn0blTMwIoFxZlsoMWK8hPO/fi45rgWOqEImnjvSRk++5WL+GgjLBgLvEi7wCMkBijhIMWtnva60ojd4MrxeS7evrmGRjJKXnPuSKEsrGbArZPskBjCAcg+3PlnQQUkFf6hvbGD3HZlJtcbs4hrx8tbDT2Ie7bmQfqpsawKY4=
- - secure: FipoX1VzZkzPUP6Gxd05DEva7cX6xKK2Wdq+Y18nNkyW2afPLXCNl5kCsNrgvbqAzbjKaP2M8+b0zwKjrFzNebqmmx1RRfZUJWUkNRF1EgE+tHytmMZW6tNcQlTlvA0KqXi4Dt6SIQ0l/DhwwNKZ80jmpiyYi/ErxIXzbVgVtYA=
- - secure: T2MbE9twIkdaor796/lDioCgb2+FP3G8lXq+lIqnjaL22WMP8yKtkjNo8ggSlvQZE7MAQHqi5LISw5MU2MI6ImTU50/pgdWreM5Cx37WWYqntcbJ0Sz7v396KGJzeqbDql1fGolHDlykfi+OJzzbIGC8cjz7iAD2RUZU95wEC5s=
- - secure: hWEQInvuanQavFCE3m6/q9BjNEFZQmLc94EWnBKTMiwUAdYgQQMLohN7K1Gc8irxYKp86F+P+XWE4lfDZNK3sqmxyk51TtT2EfmKWs+jSLq4+NBYQwXCpRELC5Irpm0GRCYthhsQSuarpVWss/0s0o7iJQaHxrSPcQiwDouIpwU=
- - secure: OllJUaR/WUu+H0FIjU7vQxU10JT4d+/FZuTqnX6ZTcXN3dXCirnabYp/j+r5OBY3QeOojOyzGfHUWYEUGH/PTxcxYjrohtFTWht9N9x+SxfX2fLqieH/kRKyDmIidsY8qKChf/LD9f+SwpXRXND/PctKhNR4C5BH57fGUEqE9FU=
- - secure: KgKnGtM4e+cVYfLn78eTWJ1q4ORv128abB72QBc/xiSh0rvxSIojVKZCXmRetQPXIl7NoIzU2IyjR1ABEZ+vA83PayTEsOr2KDRDgolSIgZSSiDFt4U2phQsxl4fX7wFv/jWlbxM2fysKBSIRAF57CwBjGhLjmpUO+5PdoR7N2s=
- - secure: IgOx4STauKnJWENQGcn2iBp32XcNd2anNR0Fua0ugjudu1+CV+IxcIhI8ohOfZEXyVK4MGTF8uXWrYtoiwyExG4mTXqpRWJCgIkncqiWlfT+8BoAGWxCQhUYub3MaNZANPgebKPJhTPQ8OwNz09gPMNkewRfAqNF05eb8FU2kGA=
- - secure: CPXP6g3c1FH4Zm4U19XaPvq9nnyNsQCXRkxiPcGqsJZsGG2QMgzPQyjiAuPqnWxxZHit/6NgzUszJC+skSgcTzDTeD6rOA0Wcxtbr/Un4RRxRnTcRc6mSEZqSu9RbAZMYur/mSQ9HDHnjFe1ok85He4s9jM1iFdgjtg1ToelEmA=
- - secure: fp9pzNe09PIyZ/8NjbMPGW1zdG3Q/KhJ+stUKqA+FRopAMX/Hh24gFIVJhFOmfr4Vhn0J8sF7RsFaR1mdzcPewliOzKxknWhGEGMcG9LFCZcv+vVK0Fxs4nUzCRtaXUt08FpsRofG0iBvfapZ7YBhK7lslqGVI+fxCd3ZXmayG8=
- - secure: NT/6qcecxmuKYOnw1Atc6hsyJlfB6XI2Z1lg7dE0PhlEVW2EpkckHjAc+5hgg8Zt7TifYm2qDQWJwblwPP0mMj3ra4ZIMaZAiG2kzQoZ5kthqwjAV9fatZvrDXi+jd9wBF2hPyiCokAQiTLmKTYjzY2FBqPO3VDLWdf9qZqRmxw=
- - secure: MjIWyfquKANh/YeoyHGksdvAUQ4wc2tBCQmq1QcRhKwb7Sy6wcDk1nujDmnGE7HFpZUS6CyoZF7AMzJGFkCzrChpsLQYUP4hc7VjkXOLzi90vJUl+ANq7KPOmxC0MjKpgeHqCysRbTYbUsnJZfbbZbIZjCAjY0YCY2pGniXpvQc=
- - secure: AsZLOiFrHkGsY6jp2ShI5kYz78V6PEUyizgtPCWTgevTRGWpdCq9csIEoqUBY+vMUxmQPC6IY4fwHkrRCbv/rJyhwRl/Rnwa3aw8bdD+YD17IxnpXKGXXUyXdTZmF7HzAkVgStehL+qWZ3x9TBdExIV37KVgrVw/b+S0QqBUlQo=
- - secure: jwEnSquLreMM1M6N3gGpgTGHd8VtjBUTLDdkrokhiH1jHLpz7Hmr6xeajhZws+2sLtLiB7hYi6WsZBE5VcymBoObh9MeodO9Ve5/1z06lFmx1DyYV6euyo9WUkU2WpoVfu8k7O+eAvyrXXZVqm8Oz1p7Isb6Bh5+fJH2H8rhed4=
- - secure: HOAK620U6mlS11XK+JtXTBk26Tt2vWO4shA/6Zit/y0/kAz7JnbXtup7FSysXliBoSv4YsxA6IbgZ8V0tuIXj+q7EcqtHMmQhqzMJG5jRKVhtGiFIhDmwmxJvdfIvwtZOO3mMk0OspLz24sWp8wCciYZMPj0hZJR04R9aWEO3cE=
- - secure: DfTRP74UWWxA460XfLoJFgRLwoKbHWNIueL6qr982AnuAxeZFofsxCqxSxcSJmu67TxuPc+b201+BmanHKYmSauGS31t0F4QXk7lCTaT/x38mAPsWvMFkY8HEl56JhmzEp2hAKDB/t0/HItwmvxT1vd5WvNRSSojEVzChftV/zE=
- - secure: JoCWsJzTgj+epgzmgbvV7/bdAPHwUGXZA7Jdvv9vIJ5lCo6h9WwCw6/KCvH+bHtrT/RfZmUmxouCxJCLKwts1ZrMmedTIXpMrQJo/YgWRp7ziFnLyZ8jG8bD7rep3ngq1x/cRGc3cZvYN6IK3GS6C27OviYLFsTw74AUnWTaFSo=
- - secure: iXfl0WnAnfKurZUrMeV1yOoFiiZ+MKx/Zj6ZVP2++A9EOxxIxb/fS/gIOzSjBQwzrR+fJVHIlX0g42CiBKDQWUvIl5I8kZCVIP6AHa1jyzlmZE9lqSlojz3k5RPS7pW6nIX+z1NHMvtb3e5xeLv8y4J5kwZErqZ+YDJmBRtPxPU=
- - secure: RhAW5kABDPB3GWKD+NCg05Kcd92F/+kg+0icXXN166DWQYUut3MLrSY80xNzkz5nXTI9EFU4fUqlKLDiF/kelr0Zp/zpCQAB54o4cu5FkZz0Bgs9k7yUdCRyz6Vt2ChV5cYI4JTn9bMaeXEaGlOjP1iE51rYT6KO6kKlwsEnjUc=
- - secure: jy/3fC+UtrDcE/X6/IxkyT2SrYMKkiEMP1ht4d5mxvNA0Xxn43E16c6FNP0JWPpWRGRIP38vnQRB4yOPU9BXvRmmswVL9Ge4e/6flJvKwD5Rlqb2dfaGaHRYV9v8Nkdzl2FvZ9eBH5KHxgG19gCG6L3RXP/+zYwrr4AQdm0fpfw=
- - secure: RYEwBWYVXRTEdUWhQxdWXo6tldlVx8pha9zB0rgafcUQxaatAefnRc4X4HXTQnqr2n9TZ2TQGpM8vte/wr6Pjc85VZbimWGzgrvn0kg4MwPR8ZYiEM5qQ/pUpj4+93rpA91PhCGvZoZTqOrXHm4kMPuKro5I6qA4BFUXuANeC/s=
- - secure: gHSicpqkqcZT04QurSgszrAiI6HOCw1DBlfIIi9KAJj7mG5GijD/4AQ6HCmcRMbCDJ0nUuvm/kckASnRtF5+3xvIJnuoyyEfCZWxt1lhK2UbS87VU+pVdws/VzwpisXuKsh3H0uT8DDVkWPH/ZWDgfVa74eYDEHiQFjo+2xx5ZA=
- - secure: Q42bco3JXEpyVbL2akiOsaCHnAagAFIb3TF6H5qJfaLLqmGs/XrrgxliNaVMfWVSwPT2wpQvg9UGF9x37No9bZBv33DgYcWExmXb/lvGPpkctX37+FTMzECQHxOuUbYPQA7ZEuJ4AA7bwgpMISUeSyz5XXz44KcXIrZK2GWH+X4=
- - secure: hugd8NVukJc3redDvlOt6zhaqa63XLNMp/eIIlNllW8VfQ6CJ1P7KJPwgxH24sDyrw7rLzOkBl6R4kaVWsCLCFp+NE6yFFHl9wDkSdLC1OX1DMrJnDsogwUqqe+jX8dxePSy26MSTfG8eo9/NxN9uXr+tKaHoi6G7BRXDHtQ8dQ=
- - secure: TRkW9pIuIYHXJmPlDYoddxIp2M2W2f7qBGNJKEMB5xrOezES7w9XTg2eQXrD8jBO+fUUmMnAaDAXZuU58nMysPXx3vhtZKncg8w5CyuXJk2P8nkdPh0u5nmRhEpWrLKtLwJrX48xmJhNQvQqDAyL5c9WUzlWJ4WJFgoP5IDWmLc=
- - secure: QHuMdtFCvttiIOx6iS+lH4bKXZMwsgVQ6FPsUW5zJ7uw6mAEWKEil9xNk4aYV9FywinwUs4fnFlnIW/Gj1gLkUjm4DtxdmRZIlRXIbgsNch6H916TCPg4Q2oPsW2nVdXPjW/2jhkfLUiSnuhL+ylami1NF8Up7vokXknh/jFNZU=
- - secure: GTfrUVmMQSxho3Ia4Y1ONqKvVMD34GHF2/TJb8UdQV7iH+nVxVXpy3nWaCXa9ri7lRzMefkoVLy0gKK13YoVd7w3d2S3/IfNakC85XfN6VuOzK/FDkA0WoPrgKjcQ64I+3dQ6cgrMWWTieKwRZy+Ve24iRbnN055Hk+VRMu6OGw=
- - secure: SOMYGVfHLkHsH6koxpw68YQ4ydEo6YXPhHbrYGQbehUbFa6+OZzBcAJRJbKjyhD2AZRvNr2jB8XnjYKvVyDGQRpkWhGYZ7CpHqINpDsqKBsbiMe3/+KmKQqS+UKxNGefquoOvyQ1N8Xy77dkWYokRtGMEuR12RkZLonxiDW8Qyg=
- - secure: bSsDg+dJnPFdFiC/tbb61HdLh/Q0z2RVVAReT1wvV1BN4fN4NydvkUGbQmyFNyyunLulEs+X0oFma9L0497nUlTnan8UOg9sIleTSybPX6E9xSKKCItH1GgDw8bM9Igez5OOrrePBD3altVrH+FmGx0dlTQgM/KZMN50BJ79cXw=
- rvm: 2.2
+ 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)
- - echo -n $DO_KEY_CHUNK_{0..30} >> ~/.ssh/id_aws.base64
- - cat ~/.ssh/id_aws.base64 | tr -d ' ' | base64 --decode > ~/.ssh/id_aws.pem
before_script:
+ - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER )
- cd kitchen-tests
script:
- - if [ "$TRAVIS_SECURE_ENV_VARS" = "true" ]; then bundle exec kitchen test centos-6; fi
+ - bundle exec kitchen test ubuntu-1404
+ after_failure:
+ - cat .kitchen/logs/kitchen.log
+ env:
+ - UBUNTU=14.04
+ - KITCHEN_YAML=.kitchen.travis.yml
+ - rvm: 2.2
+ 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)
+ before_script:
+ - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER )
+ - cd kitchen-tests
+ script:
+ - bundle exec kitchen test ubuntu-1604
+ after_failure:
+ - cat .kitchen/logs/kitchen.log
+ env:
+ - UBUNTU=16.04
+ - KITCHEN_YAML=.kitchen.travis.yml
+ - rvm: 2.2
+ 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)
+ before_script:
+ - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER )
+ - cd kitchen-tests
+ script:
+ - bundle exec kitchen test debian-7
+ after_failure:
+ - cat .kitchen/logs/kitchen.log
+ env:
+ - DEBIAN=7
+ - KITCHEN_YAML=.kitchen.travis.yml
+ - rvm: 2.2
+ 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)
+ before_script:
+ - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER )
+ - cd kitchen-tests
+ script:
+ - bundle exec kitchen test debian-8
+ after_failure:
+ - cat .kitchen/logs/kitchen.log
+ env:
+ - DEBIAN=8
+ - KITCHEN_YAML=.kitchen.travis.yml
+ - rvm: 2.2
+ 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)
+ before_script:
+ - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER )
+ - cd kitchen-tests
+ script:
+ - bundle exec kitchen test centos-6
+ after_failure:
+ - cat .kitchen/logs/kitchen.log
+ env:
+ - CENTOS=6
+ - KITCHEN_YAML=.kitchen.travis.yml
+ - rvm: 2.2
+ 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)
+ before_script:
+ - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER )
+ - cd kitchen-tests
+ script:
+ - bundle exec kitchen test centos-7
+ after_failure:
+ - cat .kitchen/logs/kitchen.log
+ env:
+ - CENTOS=7
+ - KITCHEN_YAML=.kitchen.travis.yml
+ - rvm: 2.2
+ 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)
+ before_script:
+ - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER )
+ - cd kitchen-tests
+ script:
+ - bundle exec kitchen test fedora-23
after_failure:
- cat .kitchen/logs/kitchen.log
- after_script:
- - if [ "$TRAVIS_SECURE_ENV_VARS" = "true" ]; then bundle exec kitchen destroy centos-6; fi
env:
- - CENTOS=1
+ - FEDORA=23
- KITCHEN_YAML=.kitchen.travis.yml
- - EC2_SSH_KEY_PATH=~/.ssh/id_aws.pem
- - secure: VAauyVnAMWhqvnhJOJ/tCDn3XAdWqzbWiDVQPNBkqtm2SBIvhmZl2hlrusvw6YLU31Prdf8fSFhOSysVQQs/rJYrmD/1BfV79p6M7cGXYZ0nGWwldF81N296lyFoZLyrqtmG4G0cx3Pw2ojADFgFe+B5eTGlqJFD+z371g4RF/Y=
- - secure: A+qtUF2LPJGkUAdvt04AwZMt69rzaeTyR0/1XEOAuntBKKXSCzddUzr5ePDc9QQ/57AWywKxhVLpnxk3QzKN7r7zerDxyIJBgklNDpNAKkeQjP3T6FpaKEIN9ROcpPtsM6FJ5Agb+bEQoRJF7s+ampO3wLV3XpTiWNuWkcAhv9A=
- - secure: J8JIg15trrPgc8X/1DsaUWDQCdDWTvN/AorXzZ/ReudHS6G/KpoynZ5lTmKjlgFiFNE/TGMDv486pStGtIcarTKTuIEmNADdEWlAVH7bxclpayMjtppVuapRCkZWccs5gz5CJyhX7yhQCFTYoqVox9Y4qHGCluF3oqCcPRtCOOw=
- - secure: NJYn0blTMwIoFxZlsoMWK8hPO/fi45rgWOqEImnjvSRk++5WL+GgjLBgLvEi7wCMkBijhIMWtnva60ojd4MrxeS7evrmGRjJKXnPuSKEsrGbArZPskBjCAcg+3PlnQQUkFf6hvbGD3HZlJtcbs4hrx8tbDT2Ie7bmQfqpsawKY4=
- - secure: FipoX1VzZkzPUP6Gxd05DEva7cX6xKK2Wdq+Y18nNkyW2afPLXCNl5kCsNrgvbqAzbjKaP2M8+b0zwKjrFzNebqmmx1RRfZUJWUkNRF1EgE+tHytmMZW6tNcQlTlvA0KqXi4Dt6SIQ0l/DhwwNKZ80jmpiyYi/ErxIXzbVgVtYA=
- - secure: T2MbE9twIkdaor796/lDioCgb2+FP3G8lXq+lIqnjaL22WMP8yKtkjNo8ggSlvQZE7MAQHqi5LISw5MU2MI6ImTU50/pgdWreM5Cx37WWYqntcbJ0Sz7v396KGJzeqbDql1fGolHDlykfi+OJzzbIGC8cjz7iAD2RUZU95wEC5s=
- - secure: hWEQInvuanQavFCE3m6/q9BjNEFZQmLc94EWnBKTMiwUAdYgQQMLohN7K1Gc8irxYKp86F+P+XWE4lfDZNK3sqmxyk51TtT2EfmKWs+jSLq4+NBYQwXCpRELC5Irpm0GRCYthhsQSuarpVWss/0s0o7iJQaHxrSPcQiwDouIpwU=
- - secure: OllJUaR/WUu+H0FIjU7vQxU10JT4d+/FZuTqnX6ZTcXN3dXCirnabYp/j+r5OBY3QeOojOyzGfHUWYEUGH/PTxcxYjrohtFTWht9N9x+SxfX2fLqieH/kRKyDmIidsY8qKChf/LD9f+SwpXRXND/PctKhNR4C5BH57fGUEqE9FU=
- - secure: KgKnGtM4e+cVYfLn78eTWJ1q4ORv128abB72QBc/xiSh0rvxSIojVKZCXmRetQPXIl7NoIzU2IyjR1ABEZ+vA83PayTEsOr2KDRDgolSIgZSSiDFt4U2phQsxl4fX7wFv/jWlbxM2fysKBSIRAF57CwBjGhLjmpUO+5PdoR7N2s=
- - secure: IgOx4STauKnJWENQGcn2iBp32XcNd2anNR0Fua0ugjudu1+CV+IxcIhI8ohOfZEXyVK4MGTF8uXWrYtoiwyExG4mTXqpRWJCgIkncqiWlfT+8BoAGWxCQhUYub3MaNZANPgebKPJhTPQ8OwNz09gPMNkewRfAqNF05eb8FU2kGA=
- - secure: CPXP6g3c1FH4Zm4U19XaPvq9nnyNsQCXRkxiPcGqsJZsGG2QMgzPQyjiAuPqnWxxZHit/6NgzUszJC+skSgcTzDTeD6rOA0Wcxtbr/Un4RRxRnTcRc6mSEZqSu9RbAZMYur/mSQ9HDHnjFe1ok85He4s9jM1iFdgjtg1ToelEmA=
- - secure: fp9pzNe09PIyZ/8NjbMPGW1zdG3Q/KhJ+stUKqA+FRopAMX/Hh24gFIVJhFOmfr4Vhn0J8sF7RsFaR1mdzcPewliOzKxknWhGEGMcG9LFCZcv+vVK0Fxs4nUzCRtaXUt08FpsRofG0iBvfapZ7YBhK7lslqGVI+fxCd3ZXmayG8=
- - secure: NT/6qcecxmuKYOnw1Atc6hsyJlfB6XI2Z1lg7dE0PhlEVW2EpkckHjAc+5hgg8Zt7TifYm2qDQWJwblwPP0mMj3ra4ZIMaZAiG2kzQoZ5kthqwjAV9fatZvrDXi+jd9wBF2hPyiCokAQiTLmKTYjzY2FBqPO3VDLWdf9qZqRmxw=
- - secure: MjIWyfquKANh/YeoyHGksdvAUQ4wc2tBCQmq1QcRhKwb7Sy6wcDk1nujDmnGE7HFpZUS6CyoZF7AMzJGFkCzrChpsLQYUP4hc7VjkXOLzi90vJUl+ANq7KPOmxC0MjKpgeHqCysRbTYbUsnJZfbbZbIZjCAjY0YCY2pGniXpvQc=
- - secure: AsZLOiFrHkGsY6jp2ShI5kYz78V6PEUyizgtPCWTgevTRGWpdCq9csIEoqUBY+vMUxmQPC6IY4fwHkrRCbv/rJyhwRl/Rnwa3aw8bdD+YD17IxnpXKGXXUyXdTZmF7HzAkVgStehL+qWZ3x9TBdExIV37KVgrVw/b+S0QqBUlQo=
- - secure: jwEnSquLreMM1M6N3gGpgTGHd8VtjBUTLDdkrokhiH1jHLpz7Hmr6xeajhZws+2sLtLiB7hYi6WsZBE5VcymBoObh9MeodO9Ve5/1z06lFmx1DyYV6euyo9WUkU2WpoVfu8k7O+eAvyrXXZVqm8Oz1p7Isb6Bh5+fJH2H8rhed4=
- - secure: HOAK620U6mlS11XK+JtXTBk26Tt2vWO4shA/6Zit/y0/kAz7JnbXtup7FSysXliBoSv4YsxA6IbgZ8V0tuIXj+q7EcqtHMmQhqzMJG5jRKVhtGiFIhDmwmxJvdfIvwtZOO3mMk0OspLz24sWp8wCciYZMPj0hZJR04R9aWEO3cE=
- - secure: DfTRP74UWWxA460XfLoJFgRLwoKbHWNIueL6qr982AnuAxeZFofsxCqxSxcSJmu67TxuPc+b201+BmanHKYmSauGS31t0F4QXk7lCTaT/x38mAPsWvMFkY8HEl56JhmzEp2hAKDB/t0/HItwmvxT1vd5WvNRSSojEVzChftV/zE=
- - secure: JoCWsJzTgj+epgzmgbvV7/bdAPHwUGXZA7Jdvv9vIJ5lCo6h9WwCw6/KCvH+bHtrT/RfZmUmxouCxJCLKwts1ZrMmedTIXpMrQJo/YgWRp7ziFnLyZ8jG8bD7rep3ngq1x/cRGc3cZvYN6IK3GS6C27OviYLFsTw74AUnWTaFSo=
- - secure: iXfl0WnAnfKurZUrMeV1yOoFiiZ+MKx/Zj6ZVP2++A9EOxxIxb/fS/gIOzSjBQwzrR+fJVHIlX0g42CiBKDQWUvIl5I8kZCVIP6AHa1jyzlmZE9lqSlojz3k5RPS7pW6nIX+z1NHMvtb3e5xeLv8y4J5kwZErqZ+YDJmBRtPxPU=
- - secure: RhAW5kABDPB3GWKD+NCg05Kcd92F/+kg+0icXXN166DWQYUut3MLrSY80xNzkz5nXTI9EFU4fUqlKLDiF/kelr0Zp/zpCQAB54o4cu5FkZz0Bgs9k7yUdCRyz6Vt2ChV5cYI4JTn9bMaeXEaGlOjP1iE51rYT6KO6kKlwsEnjUc=
- - secure: jy/3fC+UtrDcE/X6/IxkyT2SrYMKkiEMP1ht4d5mxvNA0Xxn43E16c6FNP0JWPpWRGRIP38vnQRB4yOPU9BXvRmmswVL9Ge4e/6flJvKwD5Rlqb2dfaGaHRYV9v8Nkdzl2FvZ9eBH5KHxgG19gCG6L3RXP/+zYwrr4AQdm0fpfw=
- - secure: RYEwBWYVXRTEdUWhQxdWXo6tldlVx8pha9zB0rgafcUQxaatAefnRc4X4HXTQnqr2n9TZ2TQGpM8vte/wr6Pjc85VZbimWGzgrvn0kg4MwPR8ZYiEM5qQ/pUpj4+93rpA91PhCGvZoZTqOrXHm4kMPuKro5I6qA4BFUXuANeC/s=
- - secure: gHSicpqkqcZT04QurSgszrAiI6HOCw1DBlfIIi9KAJj7mG5GijD/4AQ6HCmcRMbCDJ0nUuvm/kckASnRtF5+3xvIJnuoyyEfCZWxt1lhK2UbS87VU+pVdws/VzwpisXuKsh3H0uT8DDVkWPH/ZWDgfVa74eYDEHiQFjo+2xx5ZA=
- - secure: Q42bco3JXEpyVbL2akiOsaCHnAagAFIb3TF6H5qJfaLLqmGs/XrrgxliNaVMfWVSwPT2wpQvg9UGF9x37No9bZBv33DgYcWExmXb/lvGPpkctX37+FTMzECQHxOuUbYPQA7ZEuJ4AA7bwgpMISUeSyz5XXz44KcXIrZK2GWH+X4=
- - secure: hugd8NVukJc3redDvlOt6zhaqa63XLNMp/eIIlNllW8VfQ6CJ1P7KJPwgxH24sDyrw7rLzOkBl6R4kaVWsCLCFp+NE6yFFHl9wDkSdLC1OX1DMrJnDsogwUqqe+jX8dxePSy26MSTfG8eo9/NxN9uXr+tKaHoi6G7BRXDHtQ8dQ=
- - secure: TRkW9pIuIYHXJmPlDYoddxIp2M2W2f7qBGNJKEMB5xrOezES7w9XTg2eQXrD8jBO+fUUmMnAaDAXZuU58nMysPXx3vhtZKncg8w5CyuXJk2P8nkdPh0u5nmRhEpWrLKtLwJrX48xmJhNQvQqDAyL5c9WUzlWJ4WJFgoP5IDWmLc=
- - secure: QHuMdtFCvttiIOx6iS+lH4bKXZMwsgVQ6FPsUW5zJ7uw6mAEWKEil9xNk4aYV9FywinwUs4fnFlnIW/Gj1gLkUjm4DtxdmRZIlRXIbgsNch6H916TCPg4Q2oPsW2nVdXPjW/2jhkfLUiSnuhL+ylami1NF8Up7vokXknh/jFNZU=
- - secure: GTfrUVmMQSxho3Ia4Y1ONqKvVMD34GHF2/TJb8UdQV7iH+nVxVXpy3nWaCXa9ri7lRzMefkoVLy0gKK13YoVd7w3d2S3/IfNakC85XfN6VuOzK/FDkA0WoPrgKjcQ64I+3dQ6cgrMWWTieKwRZy+Ve24iRbnN055Hk+VRMu6OGw=
- - secure: SOMYGVfHLkHsH6koxpw68YQ4ydEo6YXPhHbrYGQbehUbFa6+OZzBcAJRJbKjyhD2AZRvNr2jB8XnjYKvVyDGQRpkWhGYZ7CpHqINpDsqKBsbiMe3/+KmKQqS+UKxNGefquoOvyQ1N8Xy77dkWYokRtGMEuR12RkZLonxiDW8Qyg=
- - secure: bSsDg+dJnPFdFiC/tbb61HdLh/Q0z2RVVAReT1wvV1BN4fN4NydvkUGbQmyFNyyunLulEs+X0oFma9L0497nUlTnan8UOg9sIleTSybPX6E9xSKKCItH1GgDw8bM9Igez5OOrrePBD3altVrH+FmGx0dlTQgM/KZMN50BJ79cXw=
### END TEST KITCHEN ONLY ###
- rvm: 2.2
sudo: required
@@ -246,13 +281,3 @@ notifications:
irc:
channels:
- chat.freenode.net#chef-hacking
- webhooks:
- urls:
- # Gitter IM
- - secure: HmMKr/ysKVyKUJ24PRCHcA8QCmlFoukrYumY0GRLzvaFWO8PknHO1t/0RbrKRb2ed/hgkFd+RKNCYvSvcE8Ahr2vlMrBeGHGfVeOGkWtbhLgNqo1b50Ll9CqvTM8X2ZIq6hIWraanwoYRQu/8uGL29yH4lBi7DhpTkFwBMLulhQ=
- hipchat:
- rooms:
- # Build Statuses
- - secure: G8MNo94L8bmWWwkH2/ViB2QaZnZHZscYM/mEjDbOGd15sqrruwckeARyBoUcRI7P1C6AFmS4IKCNVXa6KzX4Pbh51gQWM92zRpRTZpplwtXz53/1l8ajLFLLMLvEMTlBFAANUKEUFAQPY4dMa14V3Qc5oijfIncN61k4nZNTKpY=
- # Open Source
- - secure: hmcex4PpG5dn8WvjndONO4xCUKOC5kPU/bUEGRrfVbe2YKJE7t0XXbNDC96W/xBgzgnJzvf1Er0zJKDrNf4qEDEWFoozdN00WLcqREgaLLS3Seto2FjR/BpBk5q+sCV0rwwEMms2P4Qk+VSnDCnm9EaeM55hOabqNuOrRzoZLBQ=
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6846ff5fd3..549b63abba 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,41 @@
# Change Log
+## [v12.11.18](https://github.com/chef/chef/tree/v12.11.18) (2016-06-02)
+[Full Changelog](https://github.com/chef/chef/compare/v12.11.17...v12.11.18)
+
+**Implemented Enhancements:**
+
+- Creation of the new DataCollector reporter [\#4973](https://github.com/chef/chef/pull/4973) ([adamleff](https://github.com/adamleff))
+- Add systemd\_unit try-restart, reload-or-restart, reload-or-try-restart actions [\#4908](https://github.com/chef/chef/pull/4908) ([nathwill](https://github.com/nathwill))
+- RFC062 exit status chef client [\#4611](https://github.com/chef/chef/pull/4611) ([smurawski](https://github.com/smurawski))
+- Create 'universal' DSL [\#4942](https://github.com/chef/chef/pull/4942) ([lamont-granquist](https://github.com/lamont-granquist))
+- Handle numeric id for the user value in the git resource [\#4902](https://github.com/chef/chef/pull/4902) ([MichaelPereira](https://github.com/MichaelPereira))
+- RFC 31 - Default solo to local mode [\#4919](https://github.com/chef/chef/pull/4919) ([thommay](https://github.com/thommay))
+- Wire up chef handlers directly from libraries [\#4933](https://github.com/chef/chef/pull/4933) ([lamont-granquist](https://github.com/lamont-granquist))
+- Reject malformed ini content in systemd\_unit resource [\#4907](https://github.com/chef/chef/pull/4907) ([nathwill](https://github.com/nathwill))
+- Update usage of @new\_resource.destination to `cwd` within the git hwrp [\#4898](https://github.com/chef/chef/pull/4898) ([joshburt](https://github.com/joshburt))
+- Support Ruby Files in ChefFS [\#4887](https://github.com/chef/chef/pull/4887) ([thommay](https://github.com/thommay))
+- Adds a system check for fips enablement and runs in fips mode if enabled [\#4880](https://github.com/chef/chef/pull/4880) ([mwrock](https://github.com/mwrock))
+- Lazy'ing candidate\_version in package provider [\#4869](https://github.com/chef/chef/pull/4869) ([lamont-granquist](https://github.com/lamont-granquist))
+- Add systemd\_unit resource [\#4700](https://github.com/chef/chef/pull/4700) ([nathwill](https://github.com/nathwill))
+- Bump chef-zero to avoid aggressive logging [\#4878](https://github.com/chef/chef/pull/4878) ([stevendanna](https://github.com/stevendanna))
+
+**Fixed Bugs:**
+
+- Fix \#4949 and Avoid Errno::EBUSY on docker containers [\#4979](https://github.com/chef/chef/pull/4979) ([andrewjamesbrown](https://github.com/andrewjamesbrown))
+- Ensure recipe-url works right in solo [\#4957](https://github.com/chef/chef/pull/4957) ([thommay](https://github.com/thommay))
+- Fix portage provider to support version with character [\#4966](https://github.com/chef/chef/pull/4966) ([crigor](https://github.com/crigor))
+- Fixes \#4968 and only retrieves the latest version of packages from chocolatey [\#4977](https://github.com/chef/chef/pull/4977) ([mwrock](https://github.com/mwrock))
+- Update contributing doc to better reflect reality [\#4962](https://github.com/chef/chef/pull/4962) ([tas50](https://github.com/tas50))
+- Load cookbook versions correctly for knife [\#4936](https://github.com/chef/chef/pull/4936) ([thommay](https://github.com/thommay))
+- Gem metadata command needs Gem.clear\_paths [\#4929](https://github.com/chef/chef/pull/4929) ([lamont-granquist](https://github.com/lamont-granquist))
+- Fix os x profile provider for nil [\#4921](https://github.com/chef/chef/pull/4921) ([achand](https://github.com/achand))
+- Cookbook site install : tar error on windows [\#4867](https://github.com/chef/chef/pull/4867) ([willoucom](https://github.com/willoucom))
+- Fix yum\_package breakage \(the =~ operator in ruby is awful\) [\#4912](https://github.com/chef/chef/pull/4912) ([lamont-granquist](https://github.com/lamont-granquist))
+- Encode registry enumerated values and keys to utf8 instead of the local codepage [\#4906](https://github.com/chef/chef/pull/4906) ([mwrock](https://github.com/mwrock))
+- Chocolatey Package Provider chomps nil object [\#4760](https://github.com/chef/chef/pull/4760) ([svmastersamurai](https://github.com/svmastersamurai))
+- Fixes knife ssl check on windows [\#4886](https://github.com/chef/chef/pull/4886) ([mwrock](https://github.com/mwrock))
+
## [v12.10.24](https://github.com/chef/chef/tree/v12.10.24) (2016-04-27)
[Full Changelog](https://github.com/chef/chef/compare/v12.10.23...v12.10.24)
diff --git a/DOC_CHANGES.md b/DOC_CHANGES.md
index 90b57c7e34..069cdc62b4 100644
--- a/DOC_CHANGES.md
+++ b/DOC_CHANGES.md
@@ -6,17 +6,15 @@ Example Doc Change:
Description of the required change.
-->
-## Doc changes for Chef 12.9
+## Doc changes for Chef 12.11
-### New timeout option added to `knife ssh`
+### RFC 062 Exit Status Support
-When doing a `knife ssh` call, if a connection to a host is not able
-to succeed due to host unreachable or down, the entire call can hang. In
-order to prevent this from happening, a new timeout option has been added
-to allow a connection timeout to be passed to the underlying SSH call
-(see ConnectTimeout setting in http://linux.die.net/man/5/ssh_config)
+Starting with Chef Client 12.11, there is support for the consistent, standard exit codes as defined in [Chef RFC 062](https://github.com/chef/chef-rfc/blob/master/rfc062-exit-status.md).
+
+With no additional configuration when Chef Client exits with a non-standard exit code a deprecation warning will be issued advising users of the upcoming change in behavior.
+
+To enable the standardized exit code behavior, there is a new setting in client.rb. The `exit_status` setting, when set to `:enabled` will enforce standarized exit codes. In a future release, this will become the default behavior.
+
+If you need to maintain the previous exit code behavior to support your current workflow, you can disable this (and the deprecation warnings) by setting `exit_status` to `:disabled`.
-The timeout setting can be passed in via a command line parameter
-(`-t` or `--ssh-timeout`) or via a knife config
-(`Chef::Config[:knife][:ssh_timeout]`). The value of the timeout is set
-in seconds.
diff --git a/Gemfile b/Gemfile
index 803e1115d5..40d608e66b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -4,21 +4,16 @@ extend GemfileUtil
source "https://rubygems.org"
-# Pick the gemspec for our platform
-gemspec_name = "chef"
-Dir.glob("chef-*.gemspec").each do |gemspec_filename|
- gemspec_filename =~ /^chef-(.+).gemspec/
- gemspec_platform = $1
- if Gem::Platform.match(Gem::Platform.new(gemspec_platform))
- Bundler.ui.info "Using gemspec #{gemspec_filename} for current platform."
- gemspec_name = "chef-#{gemspec_platform}"
- end
-end
-gemspec name: gemspec_name
+# Note we do not use the gemspec DSL which restricts to the
+# gemspec for the current platform and filters out other platforms
+# during a bundle lock operation. We actually want dependencies from
+# both of our gemspecs. Also note this this mimics gemspec behavior
+# of bundler versions prior to 1.12.0 (https://github.com/bundler/bundler/commit/193a14fe5e0d56294c7b370a0e59f93b2c216eed)
+gem "chef", path: "."
gem "chef-config", path: File.expand_path("../chef-config", __FILE__) if File.exist?(File.expand_path("../chef-config", __FILE__))
# Ensure that we can always install rake, regardless of gem groups
-gem "rake"
+gem "rake", group: [ :default, :omnibus_package, :development ]
gem "bundler"
gem "cheffish"
diff --git a/Gemfile.lock b/Gemfile.lock
index 49b9f2677c..26ab929341 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -18,9 +18,9 @@ GIT
PATH
remote: .
specs:
- chef (12.11.3)
+ chef (12.11.27)
bundler (>= 1.10)
- chef-config (= 12.11.3)
+ chef-config (= 12.11.27)
chef-zero (~> 4.5)
diff-lcs (~> 1.2, >= 1.2.4)
erubis (~> 2.7)
@@ -45,9 +45,9 @@ PATH
specinfra (~> 2.10)
syslog-logger (~> 1.6)
uuidtools (~> 2.1.5)
- chef (12.11.3-universal-mingw32)
+ chef (12.11.27-universal-mingw32)
bundler (>= 1.10)
- chef-config (= 12.11.3)
+ chef-config (= 12.11.27)
chef-zero (~> 4.5)
diff-lcs (~> 1.2, >= 1.2.4)
erubis (~> 2.7)
@@ -87,7 +87,7 @@ PATH
PATH
remote: chef-config
specs:
- chef-config (12.11.3)
+ chef-config (12.11.27)
fuzzyurl (~> 0.8.0)
mixlib-config (~> 2.0)
mixlib-shellout (~> 2.0)
@@ -99,20 +99,20 @@ GEM
appbundler (0.9.0)
mixlib-cli (~> 1.4)
artifactory (2.3.2)
- ast (2.2.0)
- aws-sdk (2.3.7)
- aws-sdk-resources (= 2.3.7)
- aws-sdk-core (2.3.7)
+ ast (2.3.0)
+ aws-sdk (2.3.12)
+ aws-sdk-resources (= 2.3.12)
+ aws-sdk-core (2.3.12)
jmespath (~> 1.0)
- aws-sdk-resources (2.3.7)
- aws-sdk-core (= 2.3.7)
+ aws-sdk-resources (2.3.12)
+ aws-sdk-core (= 2.3.12)
aws-sdk-v1 (1.66.0)
json (~> 1.4)
nokogiri (>= 1.4.4)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
builder (3.2.2)
- byebug (9.0.4)
+ byebug (9.0.5)
chef-api (0.6.0)
logify (~> 0.1)
mime-types
@@ -147,7 +147,7 @@ GEM
rspec (~> 3.0)
coderay (1.1.1)
colorize (0.7.7)
- compat_resource (12.10.1)
+ compat_resource (12.10.6)
cucumber-core (1.4.0)
gherkin (~> 3.2.0)
debug_inspector (0.0.2)
@@ -158,7 +158,7 @@ GEM
erubis (2.7.0)
faraday (0.9.2)
multipart-post (>= 1.2, < 3)
- fauxhai (3.4.0)
+ fauxhai (3.5.0)
net-ssh
ffi (1.9.10)
ffi (1.9.10-x86-mingw32)
@@ -174,12 +174,11 @@ GEM
yajl-ruby (~> 1.1)
fuzzyurl (0.8.0)
gherkin (3.2.0)
- github_api (0.13.1)
+ github_api (0.14.0)
addressable (~> 2.4.0)
descendants_tracker (~> 0.0.4)
faraday (~> 0.8, < 0.10)
hashie (>= 3.4)
- multi_json (>= 1.7.5, < 2.0)
oauth2
github_changelog_generator (1.12.1)
colorize (~> 0.7)
@@ -214,18 +213,15 @@ GEM
multi_json (~> 1.10)
logify (0.2.0)
method_source (0.8.2)
- mime-types (3.0)
+ mime-types (3.1)
mime-types-data (~> 3.2015)
- mime-types-data (3.2016.0221)
- mini_portile2 (2.0.0)
- mixlib-authentication (1.4.0)
+ mime-types-data (3.2016.0521)
+ mini_portile2 (2.1.0)
+ mixlib-authentication (1.4.1)
mixlib-log
- rspec-core (~> 3.2)
- rspec-expectations (~> 3.2)
- rspec-mocks (~> 3.2)
mixlib-cli (1.6.0)
mixlib-config (2.2.1)
- mixlib-install (1.0.11)
+ mixlib-install (1.0.12)
artifactory
mixlib-shellout
mixlib-versioning
@@ -250,10 +246,12 @@ GEM
net-ssh-gateway (>= 1.2.0)
net-telnet (0.1.1)
netrc (0.11.0)
- nokogiri (1.6.7.2)
- mini_portile2 (~> 2.0.0.rc2)
- nokogiri (1.6.7.2-x86-mingw32)
- mini_portile2 (~> 2.0.0.rc2)
+ nokogiri (1.6.8)
+ mini_portile2 (~> 2.1.0)
+ pkg-config (~> 1.1.7)
+ nokogiri (1.6.8-x86-mingw32)
+ mini_portile2 (~> 2.1.0)
+ pkg-config (~> 1.1.7)
nori (2.6.0)
oauth2 (1.1.0)
faraday (>= 0.8, < 0.10)
@@ -275,8 +273,9 @@ GEM
plist (~> 3.1)
systemu (~> 2.6.4)
wmi-lite (~> 1.0)
- parser (2.3.1.0)
+ parser (2.3.1.2)
ast (~> 2.2)
+ pkg-config (1.1.7)
plist (3.2.0)
poise (2.7.0)
halite (~> 1.0)
@@ -346,7 +345,7 @@ GEM
simplecov-html (~> 0.10.0)
simplecov-html (0.10.0)
slop (3.6.0)
- specinfra (2.57.3)
+ specinfra (2.59.0)
net-scp
net-ssh (>= 2.7, < 4.0)
net-telnet
@@ -434,4 +433,4 @@ DEPENDENCIES
yard
BUNDLED WITH
- 1.11.2
+ 1.12.5
diff --git a/MAINTAINERS.md b/MAINTAINERS.md
index b0474b5f09..349ea51de1 100644
--- a/MAINTAINERS.md
+++ b/MAINTAINERS.md
@@ -36,6 +36,7 @@ To mention the team, use @chef/client-core
* [AJ Christensen](https://github.com/fujin)
* [Phil Dibowitz](https://github.com/jaymzh)
* [Jay Mundrawala](https://github.com/jaym)
+* [John Keiser](https://github.com/jkeiser)
* [Jon Cowie](https://github.com/jonlives)
* [Lamont Granquist](https://github.com/lamont-granquist)
* [Claire McQuin](https://github.com/mcquin)
@@ -65,6 +66,7 @@ To mention the team, use @chef/client-dev-tools
### Maintainers
* [Daniel DeLeo](https://github.com/danielsdeleo)
+* [John Keiser](https://github.com/jkeiser)
* [Joshua Timberman](https://github.com/jtimberman)
* [Lamont Granquist](https://github.com/lamont-granquist)
* [Steven Danna](https://github.com/stevendanna)
@@ -197,11 +199,11 @@ To mention the team, use @chef/client-nxos
### Lieutenant
-* [Carl Perry](https://github.com/edolnx)
+* [Adam Leff](https://github.com/adamleff)
### Maintainers
-* [Carl Perry](https://github.com/edolnx)
+* [Adam Leff](https://github.com/adamleff)
## Cisco IOS XR
@@ -209,11 +211,11 @@ To mention the team, use @chef/client-iosxr
### Lieutenant
-* [Carl Perry](https://github.com/edolnx)
+* [Adam Leff](https://github.com/adamleff)
### Maintainers
-* [Carl Perry](https://github.com/edolnx)
+* [Adam Leff](https://github.com/adamleff)
## Fedora
diff --git a/MAINTAINERS.toml b/MAINTAINERS.toml
index c4caeb9b97..43a52e09de 100644
--- a/MAINTAINERS.toml
+++ b/MAINTAINERS.toml
@@ -42,6 +42,7 @@ another component.
"fujin",
"jaymzh",
"jaym",
+ "jkeiser",
"jonlives",
"lamont-granquist",
"mcquin",
@@ -77,6 +78,7 @@ another component.
maintainers = [
"danielsdeleo",
+ "jkeiser",
"jtimberman",
"lamont-granquist",
"stevendanna"
diff --git a/NOTICE b/NOTICE
index 19223a40bb..932967f59c 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,7 +1,7 @@
Chef NOTICE
===========
-Developed at Opscode (http://www.opscode.com).
+Developed at Chef (https://www.chef.io).
Contributors and Copyright holders:
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index b25aa52784..4d99cec89f 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,4 +1,57 @@
*This file holds "in progress" release notes for the current release under development and is intended for consumption by the Chef Documentation team.
Please see `https://docs.chef.io/release/<major>-<minor>/release_notes.html` for the official Chef release notes.*
-# Chef Client Release Notes 12.10:
+# Chef Client Release Notes 12.11:
+
+## Support for RFC 062-based exit codes
+
+[Chef RFC 062](https://github.com/chef/chef-rfc/blob/master/rfc062-exit-status.md) identifies standard exit codes for Chef Client. As of this release, When Chef exits with a non-standard exit code, a deprecation warning will be printed.
+
+Also introduced is a new configuration setting - `exit_status`.
+
+By default in this release, `exit_status` is `nil` and the default behavior will be to warn on the use of deprecated and non-standard exit codes. `exit_status` can be set to `:enabled`, which will force chef-client to exit with the RFC defined exit codes and any non-standard exit statuses will be converted to `1` or GENERIC_FAILURE. `exit_status` can also be set to `:disabled` which preserves the old behavior of non-standardized exit status and skips the deprecation warnings.
+
+## New Data Collector functionality for run statistics
+
+The Data Collector feature is new to Chef 12.11 and is detailed in [Chef RFC 077](https://github.com/chef/chef-rfc/blob/master/rfc077-mode-agnostic-data-collection.md). It provides a unified method for sharing statistics about your Chef runs in a webhook-like manner. The Data Collector supports Chef in all its modes: Chef Client, Chef Solo (commonly referred to as "Chef Client Local Mode"), and Chef Solo legacy mode.
+
+To enable the Data Collector, specify the following settings in your client configuration file:
+
+ * `data_collector.server_url`: Required. The URL to which the Chef Client will POST the Data Collector messages
+ * `data_collector.token`: Optional. An token which will be sent in a `x-data-collector-token` HTTP header which can be used to authenticate the message.
+ * `data_collector.mode`: The Chef mode in which the Data Collector should run. For example, this allows you to only enable Data Collector in Chef Solo but not Chef Client. Available options are `:solo`, `:client`, or `:both`. Default is `:both`.
+ * `data_collector.raise_on_failure`: If enabled, Chef will raise an exception and fail to run if the Data Collector cannot be reached at the start of the Chef run. Defaults to `false`.
+ * `data_collector.organization`: Optional. In Solo mode, the `organization` field in the messages will be set to this value. Default is `chef_solo`. This field does not apply to Chef Client mode.
+
+## Replace chef-solo with chef-client local mode
+
+The default operation of `chef-solo` is now the equivalent to `chef-client -z`, but allows for the old style `chef-solo` by uttering `chef-solo --legacy-mode`. As part of this effort, environment and role files written in ruby are now fully supported by `knife upload`.
+
+## Added a `systemd_unit` resource
+
+A new `systemd_unit` resource is now available. This resource supports the following properties:
+
+* `enabled` - boolean
+* `active` - boolean
+* `masked` - boolean
+* `static` - boolean
+* `user` - String
+* `content` - String or Hash
+* `triggers_reload` - boolean
+
+It has these possible actions:
+
+* `:nothing` - default
+* `:create`
+* `:delete`
+* `:enable`
+* `:disable`
+* `:mask`
+* `:unmask`
+* `:start`
+* `:stop`
+* `:restart`
+* `:reload`
+* `:try_restart`
+* `:reload_or_restart`
+* `:reload_or_try_restart`
diff --git a/ROADMAP.md b/ROADMAP.md
index 3db4932fa9..eeec513a3f 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -4,10 +4,6 @@ This file provides direction for the project as set forth by [RFC030#Roadmap](ht
It is drafted by the Project Lead and the Lieutenants, with input from Maintainers and Contributors.
-## 2015 Q4
-* Windows - core package provider
- - Moving the remaining package providers from the Windows cookbook into core
-
## 2016 Q1
* Windows - core windows_feature
- Move windows_feature from Windows cookbook, add caching
diff --git a/VERSION b/VERSION
index 169d9bf72b..9711933199 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-12.11.3 \ No newline at end of file
+12.11.27 \ No newline at end of file
diff --git a/acceptance/Gemfile.lock b/acceptance/Gemfile.lock
index 774bd485d3..840e1a49c4 100644
--- a/acceptance/Gemfile.lock
+++ b/acceptance/Gemfile.lock
@@ -1,6 +1,6 @@
GIT
remote: git://github.com/chef/chef-acceptance.git
- revision: 47e931cec100dce8efae4369a5b03443eaf2b733
+ revision: e92ddae46d2126864698b9c8e4fc4ec2dcc46c55
specs:
chef-acceptance (0.2.0)
mixlib-shellout (~> 2.0)
@@ -11,13 +11,13 @@ GEM
specs:
addressable (2.4.0)
artifactory (2.3.2)
- aws-sdk (2.3.2)
- aws-sdk-resources (= 2.3.2)
- aws-sdk-core (2.3.2)
+ aws-sdk (2.3.9)
+ aws-sdk-resources (= 2.3.9)
+ aws-sdk-core (2.3.9)
jmespath (~> 1.0)
- aws-sdk-resources (2.3.2)
- aws-sdk-core (= 2.3.2)
- berkshelf (4.3.2)
+ aws-sdk-resources (2.3.9)
+ aws-sdk-core (= 2.3.9)
+ berkshelf (4.3.3)
addressable (~> 2.3, >= 2.3.4)
berkshelf-api-client (~> 2.0, >= 2.0.2)
buff-config (~> 1.0)
@@ -52,14 +52,14 @@ GEM
celluloid-io (0.16.2)
celluloid (>= 0.16.0)
nio4r (>= 1.1.0)
- chef-config (12.9.41)
+ chef-config (12.10.24)
fuzzyurl (~> 0.8.0)
mixlib-config (~> 2.0)
mixlib-shellout (~> 2.0)
cleanroom (1.0.0)
coderay (1.1.1)
diff-lcs (1.2.5)
- docker-api (1.26.2)
+ docker-api (1.28.0)
excon (>= 0.38.0)
json
erubis (2.7.0)
@@ -75,11 +75,12 @@ GEM
hashie (3.4.4)
hitimes (1.2.4)
httpclient (2.7.2)
- inspec (0.20.1)
+ inspec (0.22.1)
+ hashie (~> 3.4)
json (~> 1.8)
method_source (~> 0.8)
pry (~> 0)
- r-train (~> 0.11)
+ r-train (~> 0.12)
rainbow (~> 2)
rspec (~> 3)
rspec-its (~> 1.2)
@@ -95,8 +96,8 @@ GEM
multi_json
retryable (~> 2.0)
test-kitchen (~> 1.4, >= 1.4.1)
- kitchen-inspec (0.12.5)
- inspec (>= 0.14.1, < 1.0.0)
+ kitchen-inspec (0.14.0)
+ inspec (>= 0.22.0, < 1.0.0)
test-kitchen (~> 1.6)
kitchen-vagrant (0.20.0)
test-kitchen (~> 1.4)
@@ -112,7 +113,7 @@ GEM
rspec-expectations (~> 3.2)
rspec-mocks (~> 3.2)
mixlib-config (2.2.1)
- mixlib-install (1.0.11)
+ mixlib-install (1.0.12)
artifactory
mixlib-shellout
mixlib-versioning
@@ -120,7 +121,7 @@ GEM
mixlib-shellout (2.2.6)
mixlib-versioning (1.1.0)
molinillo (0.4.5)
- multi_json (1.12.0)
+ multi_json (1.12.1)
multipart-post (2.0.0)
net-scp (1.2.1)
net-ssh (>= 2.6.5)
@@ -133,8 +134,8 @@ GEM
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
- r-train (0.11.2)
- docker-api (~> 1.26.2)
+ r-train (0.12.1)
+ docker-api (~> 1.26)
json (~> 1.8)
mixlib-shellout (~> 2.0)
net-scp (~> 1.2)
@@ -143,7 +144,7 @@ GEM
winrm-fs (~> 0.3)
rainbow (2.1.0)
retryable (2.0.3)
- ridley (4.5.0)
+ ridley (4.5.1)
addressable
buff-config (~> 1.0)
buff-extensions (~> 1.0)
@@ -188,7 +189,7 @@ GEM
solve (2.0.3)
molinillo (~> 0.4.2)
semverse (~> 1.1)
- test-kitchen (1.8.0)
+ test-kitchen (1.9.0)
mixlib-install (~> 1.0, >= 1.0.4)
mixlib-shellout (>= 1.2, < 3.0)
net-scp (~> 1.1)
@@ -232,4 +233,4 @@ DEPENDENCIES
winrm-fs
BUNDLED WITH
- 1.11.2
+ 1.12.5
diff --git a/acceptance/data-collector/.acceptance/acceptance-cookbook/.gitignore b/acceptance/data-collector/.acceptance/acceptance-cookbook/.gitignore
new file mode 100644
index 0000000000..041413b040
--- /dev/null
+++ b/acceptance/data-collector/.acceptance/acceptance-cookbook/.gitignore
@@ -0,0 +1,2 @@
+nodes/
+tmp/
diff --git a/acceptance/data-collector/.acceptance/acceptance-cookbook/metadata.rb b/acceptance/data-collector/.acceptance/acceptance-cookbook/metadata.rb
new file mode 100644
index 0000000000..68fc3af2dd
--- /dev/null
+++ b/acceptance/data-collector/.acceptance/acceptance-cookbook/metadata.rb
@@ -0,0 +1,3 @@
+name 'acceptance-cookbook'
+depends "kitchen_acceptance"
+depends "data-collector-test"
diff --git a/acceptance/data-collector/.acceptance/acceptance-cookbook/recipes/destroy.rb b/acceptance/data-collector/.acceptance/acceptance-cookbook/recipes/destroy.rb
new file mode 100644
index 0000000000..7f4be2c7f7
--- /dev/null
+++ b/acceptance/data-collector/.acceptance/acceptance-cookbook/recipes/destroy.rb
@@ -0,0 +1,2 @@
+log "Running 'destroy' recipe from the acceptance-cookbook in directory '#{node['chef-acceptance']['suite-dir']}'"
+kitchen "destroy"
diff --git a/acceptance/data-collector/.acceptance/acceptance-cookbook/recipes/provision.rb b/acceptance/data-collector/.acceptance/acceptance-cookbook/recipes/provision.rb
new file mode 100644
index 0000000000..c707e874f0
--- /dev/null
+++ b/acceptance/data-collector/.acceptance/acceptance-cookbook/recipes/provision.rb
@@ -0,0 +1,2 @@
+log "Running 'provision' recipe from the acceptance-cookbook in directory '#{node['chef-acceptance']['suite-dir']}'"
+kitchen "converge"
diff --git a/acceptance/data-collector/.acceptance/acceptance-cookbook/recipes/verify.rb b/acceptance/data-collector/.acceptance/acceptance-cookbook/recipes/verify.rb
new file mode 100644
index 0000000000..e4a547272b
--- /dev/null
+++ b/acceptance/data-collector/.acceptance/acceptance-cookbook/recipes/verify.rb
@@ -0,0 +1,2 @@
+log "Running 'verify' recipe from the acceptance-cookbook in directory '#{node['chef-acceptance']['suite-dir']}'"
+kitchen "verify"
diff --git a/acceptance/data-collector/.acceptance/data-collector-test/.gitignore b/acceptance/data-collector/.acceptance/data-collector-test/.gitignore
new file mode 100644
index 0000000000..ec2a890bd3
--- /dev/null
+++ b/acceptance/data-collector/.acceptance/data-collector-test/.gitignore
@@ -0,0 +1,16 @@
+.vagrant
+Berksfile.lock
+*~
+*#
+.#*
+\#*#
+.*.sw[a-z]
+*.un~
+
+# Bundler
+Gemfile.lock
+bin/*
+.bundle/*
+
+.kitchen/
+.kitchen.local.yml
diff --git a/acceptance/data-collector/.acceptance/data-collector-test/Berksfile b/acceptance/data-collector/.acceptance/data-collector-test/Berksfile
new file mode 100644
index 0000000000..34fea2166b
--- /dev/null
+++ b/acceptance/data-collector/.acceptance/data-collector-test/Berksfile
@@ -0,0 +1,3 @@
+source 'https://supermarket.chef.io'
+
+metadata
diff --git a/acceptance/data-collector/.acceptance/data-collector-test/files/default/api.rb b/acceptance/data-collector/.acceptance/data-collector-test/files/default/api.rb
new file mode 100644
index 0000000000..3fb2c730b0
--- /dev/null
+++ b/acceptance/data-collector/.acceptance/data-collector-test/files/default/api.rb
@@ -0,0 +1,85 @@
+require "json"
+require "sinatra"
+
+class Chef
+ class Node
+ # dummy class for JSON parsing
+ end
+end
+
+module ApiHelpers
+ def self.payload_type(payload)
+ message_type = payload["message_type"]
+ status = payload["status"]
+
+ message_type == "run_converge" ? "#{message_type}.#{status}" : message_type
+ end
+end
+
+class Counter
+ def self.reset
+ @@counters = Hash.new { |h, k| h[k] = 0 }
+ end
+
+ def self.increment(payload)
+ counter_name = ApiHelpers.payload_type(payload)
+ @@counters[counter_name] += 1
+ end
+
+ def self.to_json
+ @@counters.to_json
+ end
+end
+
+class MessageCache
+ include ApiHelpers
+
+ def self.reset
+ @@message_cache = {}
+ end
+
+ def self.store(payload)
+ cache_key = ApiHelpers.payload_type(payload)
+
+ @@message_cache[cache_key] = payload
+ end
+
+ def self.fetch(cache_key)
+ @@message_cache[cache_key].to_json
+ end
+end
+
+Counter.reset
+
+get "/" do
+ "Data Collector API server"
+end
+
+get "/reset-counters" do
+ Counter.reset
+ "counters reset"
+end
+
+get "/counters" do
+ Counter.to_json
+end
+
+get "/cache/:key" do |cache_key|
+ MessageCache.fetch(cache_key)
+end
+
+get "/reset-cache" do
+ MessageCache.reset
+ "cache reset"
+end
+
+post "/data-collector/v0" do
+ body = request.body.read
+ payload = JSON.load(body)
+
+ Counter.increment(payload)
+ MessageCache.store(payload)
+
+ status 201
+ "message received"
+end
diff --git a/acceptance/data-collector/.acceptance/data-collector-test/files/default/apigemfile b/acceptance/data-collector/.acceptance/data-collector-test/files/default/apigemfile
new file mode 100644
index 0000000000..94fc334d88
--- /dev/null
+++ b/acceptance/data-collector/.acceptance/data-collector-test/files/default/apigemfile
@@ -0,0 +1,3 @@
+source "https://rubygems.org"
+
+gem "sinatra"
diff --git a/acceptance/data-collector/.acceptance/data-collector-test/files/default/client-rb-both-mode.rb b/acceptance/data-collector/.acceptance/data-collector-test/files/default/client-rb-both-mode.rb
new file mode 100644
index 0000000000..89f3555be1
--- /dev/null
+++ b/acceptance/data-collector/.acceptance/data-collector-test/files/default/client-rb-both-mode.rb
@@ -0,0 +1,4 @@
+chef_server_url "http://localhost:8889"
+node_name "data-collector-test"
+data_collector.server_url "http://localhost:9292/data-collector/v0"
+data_collector.mode :both
diff --git a/acceptance/data-collector/.acceptance/data-collector-test/files/default/client-rb-client-mode.rb b/acceptance/data-collector/.acceptance/data-collector-test/files/default/client-rb-client-mode.rb
new file mode 100644
index 0000000000..8e3f0c4845
--- /dev/null
+++ b/acceptance/data-collector/.acceptance/data-collector-test/files/default/client-rb-client-mode.rb
@@ -0,0 +1,4 @@
+chef_server_url "http://localhost:8889"
+node_name "data-collector-test"
+data_collector.server_url "http://localhost:9292/data-collector/v0"
+data_collector.mode :client
diff --git a/acceptance/data-collector/.acceptance/data-collector-test/files/default/client-rb-no-endpoint.rb b/acceptance/data-collector/.acceptance/data-collector-test/files/default/client-rb-no-endpoint.rb
new file mode 100644
index 0000000000..f8374107ea
--- /dev/null
+++ b/acceptance/data-collector/.acceptance/data-collector-test/files/default/client-rb-no-endpoint.rb
@@ -0,0 +1,2 @@
+chef_server_url "http://localhost:8889"
+node_name "data-collector-test"
diff --git a/acceptance/data-collector/.acceptance/data-collector-test/files/default/client-rb-solo-mode.rb b/acceptance/data-collector/.acceptance/data-collector-test/files/default/client-rb-solo-mode.rb
new file mode 100644
index 0000000000..904e952e85
--- /dev/null
+++ b/acceptance/data-collector/.acceptance/data-collector-test/files/default/client-rb-solo-mode.rb
@@ -0,0 +1,4 @@
+chef_server_url "http://localhost:8889"
+node_name "data-collector-test"
+data_collector.server_url "http://localhost:9292/data-collector/v0"
+data_collector.mode :solo
diff --git a/acceptance/data-collector/.acceptance/data-collector-test/files/default/config.ru b/acceptance/data-collector/.acceptance/data-collector-test/files/default/config.ru
new file mode 100644
index 0000000000..81cf29d9fb
--- /dev/null
+++ b/acceptance/data-collector/.acceptance/data-collector-test/files/default/config.ru
@@ -0,0 +1,2 @@
+require_relative "./api"
+run Sinatra::Application
diff --git a/acceptance/data-collector/.acceptance/data-collector-test/metadata.rb b/acceptance/data-collector/.acceptance/data-collector-test/metadata.rb
new file mode 100644
index 0000000000..dbd376aa83
--- /dev/null
+++ b/acceptance/data-collector/.acceptance/data-collector-test/metadata.rb
@@ -0,0 +1,7 @@
+name 'data-collector-test'
+maintainer 'Adam Leff'
+maintainer_email 'adamleff@chef.io'
+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/.acceptance/data-collector-test/recipes/default.rb b/acceptance/data-collector/.acceptance/data-collector-test/recipes/default.rb
new file mode 100644
index 0000000000..20b945db9b
--- /dev/null
+++ b/acceptance/data-collector/.acceptance/data-collector-test/recipes/default.rb
@@ -0,0 +1,38 @@
+api_root_dir = "/var/opt/data_collector_api"
+
+directory api_root_dir do
+ recursive true
+end
+
+cookbook_file ::File.join(api_root_dir, "Gemfile") do
+ source "apigemfile"
+end
+
+cookbook_file ::File.join(api_root_dir, "config.ru")
+
+cookbook_file ::File.join(api_root_dir, "api.rb")
+
+execute "bundle install --binstubs" do
+ cwd api_root_dir
+end
+
+pid_file = "/var/run/api.pid"
+running_pid = ::File.exist?(pid_file) ? ::File.read(pid_file).strip : nil
+
+execute "kill existing API process" do
+ command "kill #{running_pid}"
+ not_if { running_pid.nil? }
+end
+
+execute "start API" do
+ command "bin/rackup -D -P #{pid_file}"
+ cwd api_root_dir
+end
+
+directory "/etc/chef"
+
+["both-mode", "client-mode", "no-endpoint", "solo-mode"].each do |config_file|
+ cookbook_file "/etc/chef/#{config_file}.rb" do
+ source "client-rb-#{config_file}.rb"
+ end
+end
diff --git a/acceptance/data-collector/.kitchen.yml b/acceptance/data-collector/.kitchen.yml
new file mode 100644
index 0000000000..f719e3ea69
--- /dev/null
+++ b/acceptance/data-collector/.kitchen.yml
@@ -0,0 +1,9 @@
+verifier:
+ name: busser
+
+suites:
+ - name: default
+ includes:
+ - ubuntu-14.04
+ run_list:
+ - recipe[data-collector-test::default]
diff --git a/acceptance/data-collector/Berksfile b/acceptance/data-collector/Berksfile
new file mode 100644
index 0000000000..b8f003071b
--- /dev/null
+++ b/acceptance/data-collector/Berksfile
@@ -0,0 +1,3 @@
+source "https://supermarket.chef.io"
+
+cookbook "data-collector-test", path: File.join(File.expand_path(File.dirname(__FILE__)), ".acceptance", "data-collector-test")
diff --git a/acceptance/data-collector/Berksfile.lock b/acceptance/data-collector/Berksfile.lock
new file mode 100644
index 0000000000..39f4ce30dc
--- /dev/null
+++ b/acceptance/data-collector/Berksfile.lock
@@ -0,0 +1,6 @@
+DEPENDENCIES
+ data-collector-test
+ path: .acceptance/data-collector-test
+
+GRAPH
+ data-collector-test (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
new file mode 100644
index 0000000000..be15b96429
--- /dev/null
+++ b/acceptance/data-collector/test/integration/default/serverspec/default_spec.rb
@@ -0,0 +1,251 @@
+#
+# Author:: Adam Leff (<adamleff@chef.io)
+#
+# Copyright:: Copyright 2012-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 "json"
+require "serverspec"
+
+set :backend, :exec
+
+class Chef
+ class Node
+ # dummy class for parsing JSON action messages
+ end
+end
+
+shared_examples_for "reset counters" do
+ it "resets the counters" do
+ expect(command("curl http://localhost:9292/reset-counters").exit_status).to eq(0)
+ end
+end
+
+shared_examples_for "reset cache" do
+ it "resets the message cache" do
+ expect(command("curl http://localhost:9292/reset-cache").exit_status).to eq(0)
+ end
+end
+
+shared_examples_for "successful chef run" do |cmd|
+ include_examples "reset counters"
+ include_examples "reset cache"
+
+ it "runs chef and expects a zero exit status" do
+ expect(command(cmd).exit_status).to eq(0)
+ end
+end
+
+shared_examples_for "unsuccessful chef run" do |cmd|
+ include_examples "reset counters"
+ include_examples "reset cache"
+
+ it "runs chef and expects a non-zero exit status" do
+ expect(command(cmd).exit_status).not_to eq(0)
+ end
+end
+
+shared_examples_for "counter checks" do |counters_to_check|
+ counters_to_check.each do |counter, value|
+ it "counter #{counter} should equal #{value}" do
+ counter_values = JSON.load(command("curl http://localhost:9292/counters").stdout)
+ expect(counter_values[counter]).to eq(value)
+ end
+ end
+end
+
+shared_examples_for "run_start payload check" do
+ describe "run_start message" do
+ let(:required_fields) do
+ %w{
+ chef_server_fqdn
+ entity_uuid
+ id
+ message_version
+ message_type
+ node_name
+ organization_name
+ run_id
+ source
+ start_time
+ }
+ end
+ let(:optional_fields) { [] }
+
+ it "is not missing any required fields" do
+ payload = JSON.load(command("curl http://localhost:9292/cache/run_start").stdout)
+ missing_fields = required_fields.select { |key| !payload.key?(key) }
+ expect(missing_fields).to eq([])
+ end
+
+ it "does not have any extra fields" do
+ payload = JSON.load(command("curl http://localhost:9292/cache/run_start").stdout)
+ extra_fields = payload.keys.select { |key| !required_fields.include?(key) && !optional_fields.include?(key) }
+ expect(extra_fields).to eq([])
+ end
+ end
+end
+
+shared_examples_for "run_converge.success payload check" do
+ describe "run_converge success message" do
+ let(:required_fields) do
+ %w{
+ chef_server_fqdn
+ entity_uuid
+ id
+ end_time
+ expanded_run_list
+ message_type
+ message_version
+ node_name
+ organization_name
+ resources
+ run_id
+ run_list
+ source
+ start_time
+ status
+ total_resource_count
+ updated_resource_count
+ }
+ end
+ let(:optional_fields) { [] }
+
+ it "is not missing any required fields" do
+ payload = JSON.load(command("curl http://localhost:9292/cache/run_converge.success").stdout)
+ missing_fields = required_fields.select { |key| !payload.key?(key) }
+ expect(missing_fields).to eq([])
+ end
+
+ it "does not have any extra fields" do
+ payload = JSON.load(command("curl http://localhost:9292/cache/run_converge.success").stdout)
+ extra_fields = payload.keys.select { |key| !required_fields.include?(key) && !optional_fields.include?(key) }
+ expect(extra_fields).to eq([])
+ end
+ end
+end
+
+shared_examples_for "run_converge.failure payload check" do
+ describe "run_converge failure message" do
+ let(:required_fields) do
+ %w{
+ chef_server_fqdn
+ entity_uuid
+ error
+ id
+ end_time
+ expanded_run_list
+ message_type
+ message_version
+ node_name
+ organization_name
+ resources
+ run_id
+ run_list
+ source
+ start_time
+ status
+ total_resource_count
+ updated_resource_count
+ }
+ end
+ let(:optional_fields) { [] }
+
+ it "is not missing any required fields" do
+ payload = JSON.load(command("curl http://localhost:9292/cache/run_converge.failure").stdout)
+ missing_fields = required_fields.select { |key| !payload.key?(key) }
+ expect(missing_fields).to eq([])
+ end
+
+ it "does not have any extra fields" do
+ payload = JSON.load(command("curl http://localhost:9292/cache/run_converge.failure").stdout)
+ extra_fields = payload.keys.select { |key| !required_fields.include?(key) && !optional_fields.include?(key) }
+ expect(extra_fields).to eq([])
+ end
+ end
+end
+
+shared_examples_for "node-update payload check" do
+ describe "node update message" do
+ let(:required_fields) do
+ %w{
+ entity_name
+ entity_type
+ entity_uuid
+ id
+ message_type
+ message_version
+ organization_name
+ recorded_at
+ remote_hostname
+ requestor_name
+ requestor_type
+ run_id
+ service_hostname
+ source
+ task
+ user_agent
+ }
+ end
+ let(:optional_fields) { %{data} }
+
+ it "is not missing any required fields" do
+ payload = JSON.load(command("curl http://localhost:9292/cache/action").stdout)
+ missing_fields = required_fields.select { |key| !payload.key?(key) }
+ expect(missing_fields).to eq([])
+ end
+
+ it "does not have any extra fields" do
+ payload = JSON.load(command("curl http://localhost:9292/cache/action").stdout)
+ extra_fields = payload.keys.select { |key| !required_fields.include?(key) && !optional_fields.include?(key) }
+ expect(extra_fields).to eq([])
+ end
+ end
+end
+
+describe "CCR with no data collector URL configured" do
+ include_examples "successful chef run", "chef-client -z -c /etc/chef/no-endpoint.rb"
+ include_examples "counter checks", { "run_start" => nil, "run_converge.success" => nil, "run_converge.failure" => nil }
+end
+
+describe "CCR, local mode, config in solo mode" do
+ include_examples "successful chef run", "chef-client -z -c /etc/chef/solo-mode.rb"
+ include_examples "counter checks", { "run_start" => 1, "run_converge.success" => 1, "run_converge.failure" => nil }
+ include_examples "run_start payload check"
+ include_examples "run_converge.success payload check"
+ include_examples "node-update payload check"
+end
+
+describe "CCR, local mode, config in client mode" do
+ include_examples "successful chef run", "chef-client -z -c /etc/chef/client-mode.rb"
+ include_examples "counter checks", { "run_start" => nil, "run_converge.success" => nil, "run_converge.failure" => nil }
+end
+
+describe "CCR, local mode, config in both mode" do
+ include_examples "successful chef run", "chef-client -z -c /etc/chef/both-mode.rb"
+ include_examples "counter checks", { "run_start" => 1, "run_converge.success" => 1, "run_converge.failure" => nil }
+ include_examples "run_start payload check"
+ include_examples "run_converge.success payload check"
+ include_examples "node-update payload check"
+end
+
+describe "CCR, local mode, config in solo mode, failed run" do
+ include_examples "unsuccessful chef run", "chef-client -z -c /etc/chef/solo-mode.rb -r 'recipe[cookbook-that-does-not-exist::default]'"
+ include_examples "counter checks", { "run_start" => 1, "run_converge.success" => nil, "run_converge.failure" => 1 }
+ include_examples "run_start payload check"
+ include_examples "run_converge.failure payload check"
+ include_examples "node-update payload check"
+end
diff --git a/acceptance/vendor/bundle/bundler/gems/chef-acceptance-47e931cec100 b/acceptance/vendor/bundle/bundler/gems/chef-acceptance-47e931cec100
new file mode 160000
+Subproject 47e931cec100dce8efae4369a5b03443eaf2b73
diff --git a/acceptance/vendor/bundle/bundler/gems/chef-acceptance-e92ddae46d21 b/acceptance/vendor/bundle/bundler/gems/chef-acceptance-e92ddae46d21
new file mode 160000
+Subproject e92ddae46d2126864698b9c8e4fc4ec2dcc46c5
diff --git a/chef-config/Rakefile b/chef-config/Rakefile
index 151c2754c3..46f87c96c9 100644
--- a/chef-config/Rakefile
+++ b/chef-config/Rakefile
@@ -1,4 +1,3 @@
-require "rspec/core/rake_task"
require "chef-config/package_task"
ChefConfig::PackageTask.new(File.expand_path("..", __FILE__), "ChefConfig") do |package|
@@ -7,7 +6,12 @@ end
task :default => :spec
-desc "Run standard specs"
-RSpec::Core::RakeTask.new(:spec) do |t|
- t.pattern = FileList["spec/**/*_spec.rb"]
+begin
+ require "rspec/core/rake_task"
+ desc "Run standard specs"
+ RSpec::Core::RakeTask.new(:spec) do |t|
+ t.pattern = FileList["spec/**/*_spec.rb"]
+ end
+rescue LoadError
+ STDERR.puts "\n*** RSpec not available. (sudo) gem install rspec to run unit tests. ***\n\n"
end
diff --git a/chef-config/lib/chef-config/config.rb b/chef-config/lib/chef-config/config.rb
index 502852b648..568467456f 100644
--- a/chef-config/lib/chef-config/config.rb
+++ b/chef-config/lib/chef-config/config.rb
@@ -796,6 +796,43 @@ module ChefConfig
config_context :chefdk do
end
+ # Configuration options for Data Collector reporting. These settings allow
+ # the user to configure where to send their Data Collector data, what token
+ # to send, and whether Data Collector should report its findings in client
+ # mode vs. solo mode.
+ config_context :data_collector do
+ # Full URL to the endpoint that will receive our data. If nil, the
+ # data collector will not run.
+ # Ex: http://my-data-collector.mycompany.com/ingest
+ default :server_url, nil
+
+ # An optional pre-shared token to pass as an HTTP header (x-data-collector-token)
+ # that can be used to determine whether or not the poster of this
+ # run data should be trusted.
+ # Ex: some-uuid-here
+ default :token, nil
+
+ # The Chef mode during which Data Collector is allowed to function. This
+ # can be used to run Data Collector only when running as Chef Solo but
+ # not when using Chef Client.
+ # Options: :solo (for both Solo Legacy Mode and Client Local Mode), :client, :both
+ default :mode, :both
+
+ # When the Data Collector cannot send the "starting a run" message to
+ # the Data Collector server, the Data Collector will be disabled for that
+ # run. In some situations, such as highly-regulated environments, it
+ # may be more reasonable to prevent Chef from performing the actual run.
+ # In these situations, setting this value to true will cause the Chef
+ # run to raise an exception before starting any converge activities.
+ default :raise_on_failure, false
+
+ # A user-supplied Organization string that can be sent in payloads
+ # generated by the DataCollector when Chef is run in Solo mode. This
+ # allows users to associate their Solo nodes with faux organizations
+ # without the nodes being connected to an actual Chef Server.
+ default :organization, nil
+ end
+
configurable(:http_proxy)
configurable(:http_proxy_user)
configurable(:http_proxy_pass)
diff --git a/chef-config/lib/chef-config/version.rb b/chef-config/lib/chef-config/version.rb
index 646ea0c656..68b88b4872 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 = "12.11.3"
+ VERSION = "12.11.27"
end
#
diff --git a/ci/bundle_install.sh b/ci/bundle_install.sh
new file mode 100755
index 0000000000..6c6d76dc5d
--- /dev/null
+++ b/ci/bundle_install.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+set -evx
+
+gem environment
+bundler_version=$(grep bundler omnibus_overrides.rb | cut -d'"' -f2)
+gem install bundler -v $bundler_version --user-install --conservative
+export BUNDLE_WITHOUT=default:omnibus_package:test:pry:integration:docgen:maintenance:changelog:travis:aix:bsd:linux:mac_os_x:solaris:windows
+bundle _${bundler_version}_ install
diff --git a/ci/dependency_update.sh b/ci/dependency_update.sh
new file mode 100755
index 0000000000..9588652143
--- /dev/null
+++ b/ci/dependency_update.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+set -evx
+
+. ci/bundle_install.sh
+
+bundle exec rake dependencies
+
+git checkout .bundle/config
diff --git a/ci/version_bump.sh b/ci/version_bump.sh
new file mode 100755
index 0000000000..dd53cebfd5
--- /dev/null
+++ b/ci/version_bump.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+set -evx
+
+. ci/bundle_install.sh
+
+bundle exec rake version:bump
+
+git checkout .bundle/config
diff --git a/ci/version_show.sh b/ci/version_show.sh
new file mode 100755
index 0000000000..5348f6f090
--- /dev/null
+++ b/ci/version_show.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+cat VERSION
diff --git a/kitchen-tests/.kitchen.travis.yml b/kitchen-tests/.kitchen.travis.yml
index 682a1a5f68..99486928de 100644
--- a/kitchen-tests/.kitchen.travis.yml
+++ b/kitchen-tests/.kitchen.travis.yml
@@ -1,14 +1,16 @@
---
driver:
- name: ec2
- aws_ssh_key_id: <%= ENV['AWS_KEYPAIR_NAME'] %>
- region: "us-west-2"
- availability_zone: "us-west-2a"
- security_group_ids: ["travis-ci"]
- instance_type: "m3.medium"
+ name: dokken
+ privileged: true
+ chef_version: latest
+
+transport:
+ name: dokken
provisioner:
name: chef_github
+ root_path: /opt/kitchen
+ chef_version: latest
chef_omnibus_url: "https://omnitruck.chef.io/install.sh"
chef_omnibus_install_options: "-c current"
github_owner: "chef"
@@ -20,22 +22,96 @@ provisioner:
client_rb:
diff_disabled: true
-transport:
- ssh_key: <%= ENV['EC2_SSH_KEY_PATH'] %>
+verifier:
+ name: inspec
platforms:
- - name: ubuntu-14.04
- driver:
- # http://cloud-images.ubuntu.com/locator/ec2/
- # 14.04 amd64 us-west-2 hvm:ebs-ssd
- image_id: ami-63ac5803
- - name: centos-6
- driver:
- image_id: ami-05cf2265
+- name: debian-7
+ driver:
+ image: debian:7
+ 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
+ - RUN /bin/mkdir /var/run/sshd
+
+- name: debian-8
+ driver:
+ image: debian:8
+ pid_one_command: /bin/systemd
+ intermediate_instructions:
+ - RUN /usr/bin/apt-get update
+ - RUN /usr/bin/apt-get -y install zlib1g-dev sudo net-tools
+
+- name: centos-5
+ driver:
+ image: centos:5
+ platform: rhel
+ run_command: /sbin/init
+ intermediate_instructions:
+ - RUN yum clean all
+ - RUN yum install -y which initscripts net-tools sudo
+ - RUN sed -i -e "s/Defaults.*requiretty.*/Defaults !requiretty/g" /etc/sudoers
+
+- name: centos-6
+ driver:
+ image: centos:6
+ run_command: /sbin/init
+ intermediate_instructions:
+ - RUN yum clean all
+ - RUN yum -y install which initscripts net-tools sudo
+ - RUN sed -i -e "s/Defaults.*requiretty.*/Defaults !requiretty/g" /etc/sudoers
+
+- name: centos-7
+ driver:
+ image: centos:7
+ pid_one_command: /usr/lib/systemd/systemd
+ intermediate_instructions:
+ - RUN yum clean all
+ - RUN yum -y install which initscripts net-tools sudo
+ - RUN sed -i -e "s/Defaults.*requiretty.*/Defaults !requiretty/g" /etc/sudoers
+
+- name: fedora-23
+ driver:
+ image: fedora:23
+ pid_one_command: /usr/lib/systemd/systemd
+ intermediate_instructions:
+ - RUN dnf -y install yum which initscripts rpm-build zlib-devel net-tools sudo
+ - 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
+
+- name: ubuntu-14.04
+ driver:
+ image: ubuntu-upstart:14.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
+
+- name: ubuntu-16.04
+ driver:
+ image: ubuntu:16.04
+ pid_one_command: /bin/systemd
+ intermediate_instructions:
+ - RUN /usr/bin/apt-get update
+ - RUN /usr/bin/apt-get -y install zlib1g-dev sudo net-tools
+
+- name: opensuse-13.2
+ driver:
+ image: opensuse:13.2
+ pid_one_command: /bin/systemd
+ intermediate_instructions:
+ - RUN zypper refresh
suites:
- name: webapp
run_list:
- recipe[base::default]
- - recipe[webapp::default]
- attributes:
+# - recipe[webapp::default]
diff --git a/kitchen-tests/.kitchen.yml b/kitchen-tests/.kitchen.yml
index af7ac3cde5..c02ea55a02 100644
--- a/kitchen-tests/.kitchen.yml
+++ b/kitchen-tests/.kitchen.yml
@@ -5,6 +5,9 @@ driver:
cpus: 4
memory: 2048
+verifier:
+ name: inspec
+
provisioner:
name: chef_github
chef_omnibus_url: "https://omnitruck.chef.io/install.sh"
@@ -19,17 +22,15 @@ provisioner:
platforms:
- name: ubuntu-12.04
- name: ubuntu-14.04
- # needs updates for 16.04
- #- name: ubuntu-16.04
- # needs updates for 7.2
- #- name: centos-7.2
+ - name: ubuntu-16.04
+ - name: centos-7.2
- name: centos-6.7
# needs fixing for 5.11
- #- name: centos-5.11
+ # - name: centos-5.11
suites:
- name: webapp
run_list:
- recipe[base::default]
- - recipe[webapp::default]
+# - recipe[webapp::default]
attributes:
diff --git a/kitchen-tests/Berksfile b/kitchen-tests/Berksfile
index 23c72d5394..31e49b3e18 100644
--- a/kitchen-tests/Berksfile
+++ b/kitchen-tests/Berksfile
@@ -1,6 +1,8 @@
source "https://supermarket.getchef.com"
-cookbook "webapp", :path => "cookbooks/webapp"
-cookbook "base", :path => "cookbooks/base"
+cookbook "webapp", path: "cookbooks/webapp"
+cookbook "base", path: "cookbooks/base"
cookbook "php", "~> 1.5.0"
+
+cookbook "resolver", github: "chef-cookbooks/resolver", branch: "lcg/docker"
diff --git a/kitchen-tests/Berksfile.lock b/kitchen-tests/Berksfile.lock
index b5fa7aba13..f4a9de89e2 100644
--- a/kitchen-tests/Berksfile.lock
+++ b/kitchen-tests/Berksfile.lock
@@ -2,6 +2,10 @@ DEPENDENCIES
base
path: cookbooks/base
php (~> 1.5.0)
+ resolver
+ git: git://github.com/chef-cookbooks/resolver.git
+ revision: dd65ab8e2346cc0739c13682c74868f5b939b06a
+ branch: lcg/docker
webapp
path: cookbooks/webapp
@@ -14,7 +18,7 @@ GRAPH
apt (>= 0.0.0)
build-essential (>= 0.0.0)
chef-client (>= 0.0.0)
- fail2ban (>= 0.0.0)
+ chef_hostname (>= 0.0.0)
logrotate (>= 0.0.0)
multipackage (>= 0.0.0)
nscd (>= 0.0.0)
@@ -26,15 +30,18 @@ GRAPH
ubuntu (>= 0.0.0)
users (>= 0.0.0)
yum-epel (>= 0.0.0)
- build-essential (3.2.0)
+ build-essential (4.0.0)
+ mingw (>= 0.0.0)
seven_zip (>= 0.0.0)
- chef-client (4.5.0)
+ chef-client (4.5.2)
cron (>= 1.7.0)
logrotate (>= 1.9.0)
windows (>= 1.39.0)
chef-sugar (3.3.0)
- chef_handler (1.3.0)
- compat_resource (12.9.1)
+ chef_handler (1.4.0)
+ chef_hostname (0.4.1)
+ compat_resource (>= 0.0.0)
+ compat_resource (12.10.4)
cron (1.7.6)
database (2.3.1)
aws (>= 0.0.0)
@@ -42,12 +49,13 @@ GRAPH
mysql-chef_gem (~> 0.0)
postgresql (>= 1.0.0)
xfs (>= 0.0.0)
- fail2ban (2.3.0)
- yum-epel (>= 0.0.0)
iis (4.1.7)
windows (>= 1.34.6)
iptables (2.2.0)
logrotate (1.9.2)
+ mingw (1.0.0)
+ compat_resource (>= 0.0.0)
+ seven_zip (>= 0.0.0)
multipackage (3.0.28)
compat_resource (>= 0.0.0)
mysql (5.6.3)
@@ -55,11 +63,12 @@ GRAPH
mysql-chef_gem (0.0.5)
build-essential (>= 0.0.0)
mysql (>= 0.0.0)
- nscd (2.0.0)
+ nscd (4.0.0)
compat_resource (>= 0.0.0)
- ntp (1.11.0)
+ ntp (2.0.0)
windows (>= 1.38.0)
- ohai (3.0.1)
+ ohai (4.0.2)
+ compat_resource (>= 12.9.0)
openssh (2.0.0)
iptables (>= 1.0)
openssl (4.4.0)
@@ -75,9 +84,9 @@ GRAPH
apt (>= 1.9.0)
build-essential (>= 0.0.0)
openssl (~> 4.0)
- resolver (1.3.0)
+ resolver (1.3.1)
selinux (0.9.0)
- seven_zip (2.0.0)
+ seven_zip (2.0.1)
windows (>= 1.2.2)
sudo (2.9.0)
ubuntu (1.2.0)
@@ -88,7 +97,7 @@ GRAPH
database (~> 2.3.1)
mysql (~> 5.6.3)
php (~> 1.5.0)
- windows (1.40.0)
+ windows (1.41.0)
chef_handler (>= 0.0.0)
xfs (2.0.1)
xml (2.0.0)
diff --git a/kitchen-tests/Gemfile b/kitchen-tests/Gemfile
index acc62156ae..ad89269a75 100644
--- a/kitchen-tests/Gemfile
+++ b/kitchen-tests/Gemfile
@@ -1,10 +1,11 @@
source "https://rubygems.org"
-group :end_to_end do
- gem "berkshelf"
- gem "test-kitchen", "~> 1.4"
- gem "kitchen-appbundle-updater"
- gem "kitchen-vagrant", "~> 0.17"
- gem "kitchen-ec2", github: "test-kitchen/kitchen-ec2"
- gem "vagrant-wrapper"
-end
+gem "berkshelf"
+gem "kitchen-appbundle-updater"
+gem "kitchen-dokken"
+gem "kitchen-ec2"
+gem "kitchen-inspec"
+gem "kitchen-vagrant"
+gem "ridley"
+gem "test-kitchen"
+gem "vagrant-wrapper"
diff --git a/kitchen-tests/Gemfile.lock b/kitchen-tests/Gemfile.lock
index 69c23ad156..8ab7fbe707 100644
--- a/kitchen-tests/Gemfile.lock
+++ b/kitchen-tests/Gemfile.lock
@@ -1,26 +1,15 @@
-GIT
- remote: git://github.com/test-kitchen/kitchen-ec2.git
- revision: fec3f199a646980dc289ac6db9f90e9a9e4b0f6b
- specs:
- kitchen-ec2 (1.0.0)
- aws-sdk (~> 2)
- excon
- multi_json
- retryable (~> 2.0)
- test-kitchen (~> 1.4, >= 1.4.1)
-
GEM
remote: https://rubygems.org/
specs:
addressable (2.4.0)
artifactory (2.3.2)
- aws-sdk (2.3.2)
- aws-sdk-resources (= 2.3.2)
- aws-sdk-core (2.3.2)
+ aws-sdk (2.3.9)
+ aws-sdk-resources (= 2.3.9)
+ aws-sdk-core (2.3.9)
jmespath (~> 1.0)
- aws-sdk-resources (2.3.2)
- aws-sdk-core (= 2.3.2)
- berkshelf (4.3.2)
+ aws-sdk-resources (2.3.9)
+ aws-sdk-core (= 2.3.9)
+ berkshelf (4.3.3)
addressable (~> 2.3, >= 2.3.4)
berkshelf-api-client (~> 2.0, >= 2.0.2)
buff-config (~> 1.0)
@@ -49,34 +38,71 @@ GEM
buff-ruby_engine (0.1.0)
buff-shell_out (0.2.0)
buff-ruby_engine (~> 0.1.0)
+ builder (3.2.2)
celluloid (0.16.0)
timers (~> 4.0.0)
celluloid-io (0.16.2)
celluloid (>= 0.16.0)
nio4r (>= 1.1.0)
- chef-config (12.9.41)
+ chef-config (12.10.24)
fuzzyurl (~> 0.8.0)
mixlib-config (~> 2.0)
mixlib-shellout (~> 2.0)
cleanroom (1.0.0)
+ coderay (1.1.1)
diff-lcs (1.2.5)
+ docker-api (1.26.2)
+ excon (>= 0.38.0)
+ json
erubis (2.7.0)
excon (0.49.0)
faraday (0.9.2)
multipart-post (>= 1.2, < 3)
- ffi (1.9.10-x86-mingw32)
+ ffi (1.9.10)
fuzzyurl (0.8.0)
+ gssapi (1.2.0)
+ ffi (>= 1.0.1)
+ gyoku (1.3.1)
+ builder (>= 2.1.2)
hashie (3.4.4)
hitimes (1.2.4)
hitimes (1.2.4-x86-mingw32)
httpclient (2.7.2)
+ inspec (0.22.1)
+ hashie (~> 3.4)
+ json (~> 1.8)
+ method_source (~> 0.8)
+ pry (~> 0)
+ r-train (~> 0.12)
+ rainbow (~> 2)
+ rspec (~> 3)
+ rspec-its (~> 1.2)
+ rubyzip (~> 1.1)
+ thor (~> 0.19)
jmespath (1.2.4)
json_pure (>= 1.8.1)
json (1.8.3)
json_pure (1.8.3)
kitchen-appbundle-updater (0.1.2)
+ kitchen-dokken (0.0.29)
+ docker-api (~> 1.26.2)
+ test-kitchen (~> 1.5)
+ kitchen-ec2 (1.0.0)
+ aws-sdk (~> 2)
+ excon
+ multi_json
+ retryable (~> 2.0)
+ test-kitchen (~> 1.4, >= 1.4.1)
+ kitchen-inspec (0.14.0)
+ inspec (>= 0.22.0, < 1.0.0)
+ test-kitchen (~> 1.6)
kitchen-vagrant (0.20.0)
test-kitchen (~> 1.4)
+ little-plugger (1.1.4)
+ logging (2.1.0)
+ little-plugger (~> 1.1)
+ multi_json (~> 1.10)
+ method_source (0.8.2)
minitar (0.5.4)
mixlib-authentication (1.4.0)
mixlib-log
@@ -84,7 +110,7 @@ GEM
rspec-expectations (~> 3.2)
rspec-mocks (~> 3.2)
mixlib-config (2.2.1)
- mixlib-install (1.0.11)
+ mixlib-install (1.0.12)
artifactory
mixlib-shellout
mixlib-versioning
@@ -95,16 +121,30 @@ GEM
wmi-lite (~> 1.0)
mixlib-versioning (1.1.0)
molinillo (0.4.5)
- multi_json (1.12.0)
+ multi_json (1.12.1)
multipart-post (2.0.0)
net-scp (1.2.1)
net-ssh (>= 2.6.5)
net-ssh (3.1.1)
nio4r (1.2.1)
+ nori (2.6.0)
octokit (4.3.0)
sawyer (~> 0.7.0, >= 0.5.3)
+ pry (0.10.3)
+ coderay (~> 1.1.0)
+ method_source (~> 0.8.1)
+ slop (~> 3.4)
+ r-train (0.12.1)
+ docker-api (~> 1.26)
+ json (~> 1.8)
+ mixlib-shellout (~> 2.0)
+ net-scp (~> 1.2)
+ net-ssh (>= 2.9, < 4.0)
+ winrm (~> 1.6)
+ winrm-fs (~> 0.3)
+ rainbow (2.1.0)
retryable (2.0.3)
- ridley (4.5.0)
+ ridley (4.5.1)
addressable
buff-config (~> 1.0)
buff-extensions (~> 1.0)
@@ -122,24 +162,34 @@ GEM
retryable (~> 2.0)
semverse (~> 1.1)
varia_model (~> 0.4.0)
+ rspec (3.4.0)
+ rspec-core (~> 3.4.0)
+ rspec-expectations (~> 3.4.0)
+ rspec-mocks (~> 3.4.0)
rspec-core (3.4.4)
rspec-support (~> 3.4.0)
rspec-expectations (3.4.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.4.0)
+ rspec-its (1.2.0)
+ rspec-core (>= 3.0.0)
+ rspec-expectations (>= 3.0.0)
rspec-mocks (3.4.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.4.0)
rspec-support (3.4.1)
+ rubyntlm (0.6.0)
+ rubyzip (1.2.0)
safe_yaml (1.0.4)
sawyer (0.7.0)
addressable (>= 2.3.5, < 2.5)
faraday (~> 0.8, < 0.10)
semverse (1.2.1)
+ slop (3.6.0)
solve (2.0.3)
molinillo (~> 0.4.2)
semverse (~> 1.1)
- test-kitchen (1.8.0)
+ test-kitchen (1.9.0)
mixlib-install (~> 1.0, >= 1.0.4)
mixlib-shellout (>= 1.2, < 3.0)
net-scp (~> 1.1)
@@ -155,6 +205,19 @@ GEM
hashie (>= 2.0.2, < 4.0.0)
win32-process (0.8.3)
ffi (>= 1.0.0)
+ winrm (1.8.1)
+ builder (>= 2.1.2)
+ gssapi (~> 1.2)
+ gyoku (~> 1.0)
+ httpclient (~> 2.2, >= 2.2.0.2)
+ logging (>= 1.6.1, < 3.0)
+ nori (~> 2.0)
+ rubyntlm (~> 0.6.0)
+ winrm-fs (0.4.2)
+ erubis (~> 2.7)
+ logging (>= 1.6.1, < 3.0)
+ rubyzip (~> 1.1)
+ winrm (~> 1.5)
wmi-lite (1.0.0)
PLATFORMS
@@ -164,10 +227,13 @@ PLATFORMS
DEPENDENCIES
berkshelf
kitchen-appbundle-updater
- kitchen-ec2!
- kitchen-vagrant (~> 0.17)
- test-kitchen (~> 1.4)
+ kitchen-dokken
+ kitchen-ec2
+ kitchen-inspec
+ kitchen-vagrant
+ ridley
+ test-kitchen
vagrant-wrapper
BUNDLED WITH
- 1.12.1
+ 1.12.5
diff --git a/kitchen-tests/cookbooks/base/attributes/default.rb b/kitchen-tests/cookbooks/base/attributes/default.rb
index d4e5d1ee5a..ef273c969c 100644
--- a/kitchen-tests/cookbooks/base/attributes/default.rb
+++ b/kitchen-tests/cookbooks/base/attributes/default.rb
@@ -1,9 +1,10 @@
+puts "CHEF SUGAR THINKS WE ARE ON UBUNTU" if ubuntu?
+puts "CHEF SUGAR THINKS WE ARE ON RHEL" if rhel?
+
#
# ubuntu cookbook overrides
#
-default["ubuntu"]["archive_url"] = "mirror://mirrors.ubuntu.com/mirrors.txt"
-default["ubuntu"]["security_url"] = "mirror://mirrors.ubuntu.com/mirrors.txt"
default["ubuntu"]["include_source_packages"] = true
default["ubuntu"]["components"] = "main restricted universe multiverse"
@@ -78,3 +79,9 @@ default["resolver"]["search"] = "chef.io"
default["authorization"]["sudo"]["passwordless"] = true
default["authorization"]["sudo"]["users"] = %w{vagrant centos ubuntu}
+
+#
+# nscd cookbook overrides
+#
+
+default["nscd"]["server_user"] = "nobody"
diff --git a/kitchen-tests/cookbooks/base/libraries/chef-sugar.rb b/kitchen-tests/cookbooks/base/libraries/chef-sugar.rb
new file mode 100644
index 0000000000..90d02a361f
--- /dev/null
+++ b/kitchen-tests/cookbooks/base/libraries/chef-sugar.rb
@@ -0,0 +1,4 @@
+require "chef/sugar"
+
+# hack until this gets baked into chef-sugar so we can use chef-sugar in attributes files
+Chef::Node.send(:include, Chef::Sugar::DSL)
diff --git a/kitchen-tests/cookbooks/base/metadata.rb b/kitchen-tests/cookbooks/base/metadata.rb
index 9e5e792f89..3811fe914d 100644
--- a/kitchen-tests/cookbooks/base/metadata.rb
+++ b/kitchen-tests/cookbooks/base/metadata.rb
@@ -6,10 +6,12 @@ description "Installs/Configures base"
long_description "Installs/Configures base"
version "0.1.0"
+gem "chef-sugar"
+
depends "apt"
depends "build-essential"
depends "chef-client"
-depends "fail2ban"
+depends "chef_hostname"
depends "logrotate"
depends "multipackage"
depends "nscd"
diff --git a/kitchen-tests/cookbooks/base/recipes/default.rb b/kitchen-tests/cookbooks/base/recipes/default.rb
index 4ddd7a7b04..053a689b27 100644
--- a/kitchen-tests/cookbooks/base/recipes/default.rb
+++ b/kitchen-tests/cookbooks/base/recipes/default.rb
@@ -5,13 +5,18 @@
# Copyright (C) 2014
#
-if node[:platform_family] == "debian"
- include_recipe "apt"
+hostname "chef-travis-ci.chef.io"
+
+if node["platform_family"] == "debian"
include_recipe "ubuntu"
+ apt_update "packages"
end
-if %w{rhel fedora}.include?(node[:platform_family])
+if %w{rhel fedora}.include?(node["platform_family"])
include_recipe "selinux::disabled"
+end
+
+if node["platform_family"] == "rhel"
include_recipe "yum-epel"
end
@@ -31,9 +36,10 @@ include_recipe "chef-client::delete_validation"
include_recipe "chef-client::config"
include_recipe "chef-client"
-include_recipe "openssh"
+# hack needed for debian-7 on docker
+directory "/var/run/sshd"
-include_recipe "fail2ban"
+include_recipe "openssh"
include_recipe "nscd"
diff --git a/kitchen-tests/cookbooks/base/recipes/packages.rb b/kitchen-tests/cookbooks/base/recipes/packages.rb
index f242951a4c..c3a552b05c 100644
--- a/kitchen-tests/cookbooks/base/recipes/packages.rb
+++ b/kitchen-tests/cookbooks/base/recipes/packages.rb
@@ -1,6 +1,6 @@
-pkgs = %w{lsof tcpdump strace zsh dmidecode ltrace bc curl wget telnet subversion git traceroute htop iptraf tmux s3cmd sysbench }
+pkgs = %w{lsof tcpdump strace zsh dmidecode ltrace bc curl wget telnet subversion git traceroute htop tmux s3cmd sysbench }
# this deliberately calls the multipackage API N times in order to do one package installation in order to exercise the
# multipackage cookbook.
diff --git a/kitchen-tests/test/integration/webapp/default_spec.rb b/kitchen-tests/test/integration/webapp/default_spec.rb
new file mode 100644
index 0000000000..ec23a57998
--- /dev/null
+++ b/kitchen-tests/test/integration/webapp/default_spec.rb
@@ -0,0 +1,118 @@
+#describe port(80) do
+# it { should be_listening }
+# its('processes') {should include 'http'}
+#end
+#
+#describe command("curl http://localhost/index.html") do
+# its("stdout") { should match /Hello, World!/ }
+#end
+
+case os[:family]
+when "debian", "ubuntu"
+ ssh_package = "openssh-client"
+ ssh_service = "ssh"
+ ntp_service = "ntp"
+when "centos", "redhat", "fedora"
+ ssh_package = "openssh-clients"
+ ssh_service = "sshd"
+ ntp_service = "ntpd"
+else
+ raise "i don't know the family #{os[:family]}"
+end
+
+describe package("nscd") do
+ it { should be_installed }
+end
+
+describe service("nscd") do
+ # broken?
+ # it { should be_enabled }
+ it { should be_installed }
+ it { should be_running }
+end
+
+describe package(ssh_package) do
+ it { should be_installed }
+end
+
+describe service(ssh_service) do
+ it { should be_enabled }
+ it { should be_installed }
+ it { should be_running }
+end
+
+describe sshd_config do
+ its("Protocol") { should cmp 2 }
+ its("GssapiAuthentication") { should cmp "no" }
+ its("UseDns") { should cmp "no" }
+end
+
+describe ssh_config do
+ its("StrictHostKeyChecking") { should cmp "no" }
+ its("GssapiAuthentication") { should cmp "no" }
+end
+
+describe package("ntp") do
+ it { should be_installed }
+end
+
+describe service(ntp_service) do
+ # broken?
+ # it { should be_enabled }
+ it { should be_installed }
+ it { should be_running }
+end
+
+describe service("chef-client") do
+ it { should be_enabled }
+ it { should be_installed }
+ it { should be_running }
+end
+
+describe file("/etc/resolv.conf") do
+ its("content") { should match /search\s+chef.io/ }
+ its("content") { should match /nameserver\s+8.8.8.8/ }
+ its("content") { should match /nameserver\s+8.8.4.4/ }
+end
+
+describe package("gcc") do
+ it { should be_installed }
+end
+
+describe package("flex") do
+ it { should be_installed }
+end
+
+describe package("bison") do
+ it { should be_installed }
+end
+
+describe package("autoconf") do
+ it { should be_installed }
+end
+
+%w{lsof tcpdump strace zsh dmidecode ltrace bc curl wget telnet subversion git traceroute htop tmux s3cmd sysbench }.each do |pkg|
+ describe package pkg do
+ it { should be_installed }
+ end
+end
+
+describe etc_group.where(group_name: "sysadmin") do
+ its("users") { should include "adam" }
+ its("gids") { should eq [2300] }
+end
+
+describe passwd.users("adam") do
+ its("uids") { should eq ["666"] }
+end
+
+describe ntp_conf do
+ its("server") { should_not eq nil }
+end
+
+# busted inside of docker containers?
+describe port(22) do
+ it { should be_listening }
+ its("protocols") { should include "tcp" }
+ its("processes") { should eq ["sshd"] }
+end
diff --git a/kitchen-tests/test/integration/webapp/serverspec/Gemfile b/kitchen-tests/test/integration/webapp/serverspec/Gemfile
deleted file mode 100644
index eef1450f7a..0000000000
--- a/kitchen-tests/test/integration/webapp/serverspec/Gemfile
+++ /dev/null
@@ -1,4 +0,0 @@
-# This Gemfile is only needed so that busser will install gems it needs for serverspec_helper.rb to work
-source "https://rubygems.org"
-
-gem "ffi-yajl", "~> 1.1" # Go away, JSON gem
diff --git a/kitchen-tests/test/integration/webapp/serverspec/Gemfile.lock b/kitchen-tests/test/integration/webapp/serverspec/Gemfile.lock
deleted file mode 100644
index ac6c11f28c..0000000000
--- a/kitchen-tests/test/integration/webapp/serverspec/Gemfile.lock
+++ /dev/null
@@ -1,19 +0,0 @@
-GEM
- remote: https://rubygems.org/
- specs:
- ffi (1.9.10)
- ffi (1.9.10-x86-mingw32)
- ffi-yajl (1.4.0)
- ffi (~> 1.5)
- libyajl2 (~> 1.2)
- libyajl2 (1.2.0)
-
-PLATFORMS
- ruby
- x86-mingw32
-
-DEPENDENCIES
- ffi-yajl (~> 1.1)
-
-BUNDLED WITH
- 1.11.2
diff --git a/kitchen-tests/test/integration/webapp/serverspec/localhost/default_spec.rb b/kitchen-tests/test/integration/webapp/serverspec/localhost/default_spec.rb
deleted file mode 100644
index 992e4f7683..0000000000
--- a/kitchen-tests/test/integration/webapp/serverspec/localhost/default_spec.rb
+++ /dev/null
@@ -1,127 +0,0 @@
-
-require "net/http"
-require "uri"
-
-require "#{ENV['BUSSER_ROOT']}/../kitchen/data/serverspec_helper"
-
-describe "webapp::default", :end_to_end => true do
-
- describe "installed packages" do
- shared_examples_for "a package" do
- it "is installed" do
- expect(package(package_name)).to be_installed
- end
- end
-
- describe "#{property[:apache][:package]} package" do
- include_examples "a package" do
- let(:package_name) { property[:apache][:package] }
- end
- end
-
- describe "#{property[:mysql][:server_package]} package" do
- include_examples "a package" do
- let(:package_name) { property[:mysql][:server_package] }
- end
- end
-
- describe "#{property[:mysql][:client_package]} package" do
- include_examples "a package" do
- let(:package_name) { property[:mysql][:client_package] }
- end
- end
-
- describe "php package" do
- include_examples "a package" do
- let(:package_name) { property[:php][:package] }
- end
- end
- end
-
- describe "enabled/running services" do
- shared_examples_for "a service" do
- it "is enabled" do
- expect(service(service_name)).to be_enabled
- end
-
- it "is running" do
- expect(service(service_name)).to be_enabled
- end
- end
-
- describe "#{property[:apache][:service_name]} service" do
- include_examples "a service" do
- let(:service_name) { property[:apache][:service_name] }
- end
- end
-
- describe "mysql service" do
- include_examples "a service" do
- let(:service_name) { property[:mysql][:service_name] }
- end
- end
-
- end
-
- describe "mysql database" do
- let(:db_query) { "mysql -u root -pilikerandompasswordstoo -e \"#{statement}\"" }
- let(:statement) { "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME='webapp'" }
- it "creates a database called 'webapp'" do
- expect(command(db_query).stdout).to match /webapp/
- end
-
- describe "mysql database user 'webapp'" do
- let(:statement) { "SELECT Host, Db FROM mysql.db WHERE User='webapp'\\G" }
- it "adds user 'webapp' to database 'webapp@localhost'" do
- expect(command(db_query).stdout).to match /Host: localhost\n Db: webapp/
- end
-
- describe "grants" do
- shared_examples_for "a privilege" do |priv|
- let(:statement) {
- "SELECT #{priv_query}" \
- " FROM mysql.db" \
- " WHERE Host='localhost' AND Db='webapp' AND User='webapp'\\G"
- }
- let(:priv_query) { "#{priv.capitalize}_priv" }
-
- it "has privilege #{priv} on 'webapp@localhost'" do
- expect(command(db_query).stdout).to match /#{priv_query}: Y/
- end
- end
-
- %w{select update insert delete create}.each do |priv|
- include_examples "a privilege", priv do
- end
- end
- end
- end
- end
-
- describe "generated webpages" do
- let(:get_response) { Net::HTTP.get_response(uri) }
- shared_examples_for "a webpage" do
- it "exists" do
- expect(get_response).to be_kind_of(Net::HTTPSuccess)
- end
-
- it "displays content" do
- expect(get_response.body).to include(content)
- end
- end
-
- describe "http://localhost/index.html" do
- include_examples "a webpage" do
- let(:uri) { URI.parse("http://localhost/index.html") }
- let(:content) { "Hello, World!" }
- end
- end
-
- describe "http://localhost/index.php" do
- include_examples "a webpage" do
- let(:uri) { URI.parse("http://localhost/index.php") }
- let(:content) { "Hello, World!" }
- end
- end
- end
-end
diff --git a/kitchen-tests/vendor/bundle/bundler/gems/kitchen-ec2-fec3f199a646 b/kitchen-tests/vendor/bundle/bundler/gems/kitchen-ec2-fec3f199a646
new file mode 160000
+Subproject fec3f199a646980dc289ac6db9f90e9a9e4b0f6
diff --git a/lib/chef/application.rb b/lib/chef/application.rb
index 7dbffd8dec..f8df71f723 100644
--- a/lib/chef/application.rb
+++ b/lib/chef/application.rb
@@ -27,6 +27,7 @@ require "chef/platform"
require "mixlib/cli"
require "tmpdir"
require "rbconfig"
+require "chef/application/exit_code"
class Chef
class Application
@@ -60,11 +61,11 @@ class Chef
def setup_signal_handlers
trap("INT") do
- Chef::Application.fatal!("SIGINT received, stopping", 2)
+ Chef::Application.fatal!("SIGINT received, stopping", Chef::Exceptions::SigInt.new)
end
trap("TERM") do
- Chef::Application.fatal!("SIGTERM received, stopping", 3)
+ Chef::Application.fatal!("SIGTERM received, stopping", Chef::Exceptions::SigTerm.new)
end
unless Chef::Platform.windows?
@@ -149,7 +150,7 @@ class Chef
Chef::Log.level = resolve_log_level
rescue StandardError => error
Chef::Log.fatal("Failed to open or create log file at #{Chef::Config[:log_location]}: #{error.class} (#{error.message})")
- Chef::Application.fatal!("Aborting due to invalid 'log_location' configuration", 2)
+ Chef::Application.fatal!("Aborting due to invalid 'log_location' configuration", error)
end
# Turn `log_location :syslog` and `log_location :win_evt` into the
@@ -285,7 +286,7 @@ class Chef
@chef_client.run
rescue Exception => e
Chef::Log.error(e.to_s)
- exit 1
+ exit Chef::Application.normalize_exit_code(e)
else
exit 0
end
@@ -314,7 +315,7 @@ class Chef
Chef::Log.fatal("Configuration error #{error.class}: #{error.message}")
filtered_trace = error.backtrace.grep(/#{Regexp.escape(config_file_path)}/)
filtered_trace.each { |line| Chef::Log.fatal(" " + line ) }
- Chef::Application.fatal!("Aborting due to error in '#{config_file_path}'", 2)
+ Chef::Application.fatal!("Aborting due to error in '#{config_file_path}'", error)
end
# This is a hook for testing
@@ -341,15 +342,19 @@ class Chef
true
end
+ def normalize_exit_code(exit_code)
+ Chef::Application::ExitCode.normalize_exit_code(exit_code)
+ end
+
# Log a fatal error message to both STDERR and the Logger, exit the application
- def fatal!(msg, err = -1)
+ def fatal!(msg, err = nil)
Chef::Log.fatal(msg)
- Process.exit err
+ Process.exit(normalize_exit_code(err))
end
- def exit!(msg, err = -1)
+ def exit!(msg, err = nil)
Chef::Log.debug(msg)
- Process.exit err
+ Process.exit(normalize_exit_code(err))
end
end
diff --git a/lib/chef/application/apply.rb b/lib/chef/application/apply.rb
index 37ddcb3164..3e3fb58448 100644
--- a/lib/chef/application/apply.rb
+++ b/lib/chef/application/apply.rb
@@ -137,11 +137,11 @@ class Chef::Application::Apply < Chef::Application
def read_recipe_file(file_name)
if file_name.nil?
- Chef::Application.fatal!("No recipe file was provided", 1)
+ Chef::Application.fatal!("No recipe file was provided", Chef::Exceptions::RecipeNotFound.new)
else
recipe_path = File.expand_path(file_name)
unless File.exist?(recipe_path)
- Chef::Application.fatal!("No file exists at #{recipe_path}", 1)
+ Chef::Application.fatal!("No file exists at #{recipe_path}", Chef::Exceptions::RecipeNotFound.new)
end
recipe_fh = open(recipe_path)
recipe_text = recipe_fh.read
@@ -183,7 +183,7 @@ class Chef::Application::Apply < Chef::Application
else
if !ARGV[0]
puts opt_parser
- Chef::Application.exit! "No recipe file provided", 1
+ Chef::Application.exit! "No recipe file provided", Chef::Exceptions::RecipeNotFound.new
end
@recipe_filename = ARGV[0]
@recipe_text, @recipe_fh = read_recipe_file @recipe_filename
@@ -208,7 +208,7 @@ class Chef::Application::Apply < Chef::Application
raise
rescue Exception => e
Chef::Application.debug_stacktrace(e)
- Chef::Application.fatal!("#{e.class}: #{e.message}", 1)
+ Chef::Application.fatal!("#{e.class}: #{e.message}", e)
end
end
diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb
index ac46e533dd..77c86ad559 100644
--- a/lib/chef/application/client.rb
+++ b/lib/chef/application/client.rb
@@ -324,7 +324,7 @@ class Chef::Application::Client < Chef::Application
if Chef::Config[:recipe_url]
if !Chef::Config.local_mode
- Chef::Application.fatal!("chef-client recipe-url can be used only in local-mode", 1)
+ Chef::Application.fatal!("chef-client recipe-url can be used only in local-mode")
else
if Chef::Config[:delete_entire_chef_repo]
Chef::Log.debug "Cleanup path #{Chef::Config.chef_repo_path} before extract recipes into it"
@@ -420,7 +420,7 @@ class Chef::Application::Client < Chef::Application
rescue SystemExit
raise
rescue Exception => e
- Chef::Application.fatal!("#{e.class}: #{e.message}", 1)
+ Chef::Application.fatal!("#{e.class}: #{e.message}", e)
end
else
interval_run_chef_client
@@ -463,7 +463,7 @@ class Chef::Application::Client < Chef::Application
retry
end
- Chef::Application.fatal!("#{e.class}: #{e.message}", 1)
+ Chef::Application.fatal!("#{e.class}: #{e.message}", e)
end
def test_signal
diff --git a/lib/chef/application/exit_code.rb b/lib/chef/application/exit_code.rb
new file mode 100644
index 0000000000..753f1a0d80
--- /dev/null
+++ b/lib/chef/application/exit_code.rb
@@ -0,0 +1,226 @@
+#
+# Author:: Steven Murawski (<smurawski@chef.io>)
+# Copyright:: Copyright 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
+ class Application
+
+ # These are the exit codes defined in Chef RFC 062
+ # https://github.com/chef/chef-rfc/blob/master/rfc062-exit-status.md
+ class ExitCode
+
+ # -1 is defined as DEPRECATED_FAILURE in RFC 062, so it is
+ # not enumerated in an active constant.
+ #
+ VALID_RFC_062_EXIT_CODES = {
+ SUCCESS: 0,
+ GENERIC_FAILURE: 1,
+ SIGINT_RECEIVED: 2,
+ SIGTERM_RECEIVED: 3,
+ REBOOT_SCHEDULED: 35,
+ REBOOT_NEEDED: 37,
+ REBOOT_FAILED: 41,
+ AUDIT_MODE_FAILURE: 42,
+ }
+
+ DEPRECATED_RFC_062_EXIT_CODES = {
+ DEPRECATED_FAILURE: -1,
+ }
+
+ 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)
+ else
+ normalize_exit_code_to_rfc(exit_code)
+ 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 Fixnum
+ exit_code
+ 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
+
+ def lookup_exit_code_by_exception(exception)
+ if sigint_received?(exception)
+ 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)
+ VALID_RFC_062_EXIT_CODES[:REBOOT_NEEDED]
+ elsif reboot_failed?(exception)
+ VALID_RFC_062_EXIT_CODES[:REBOOT_FAILED]
+ elsif audit_failure?(exception)
+ VALID_RFC_062_EXIT_CODES[:AUDIT_MODE_FAILURE]
+ else
+ VALID_RFC_062_EXIT_CODES[:GENERIC_FAILURE]
+ 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
+ end
+ end
+
+ def reboot_needed?(exception)
+ resolve_exception_array(exception).any? do |e|
+ e.is_a? Chef::Exceptions::RebootPending
+ end
+ end
+
+ def reboot_failed?(exception)
+ resolve_exception_array(exception).any? do |e|
+ e.is_a? Chef::Exceptions::RebootFailed
+ end
+ end
+
+ def audit_failure?(exception)
+ resolve_exception_array(exception).any? do |e|
+ e.is_a? Chef::Exceptions::AuditError
+ end
+ end
+
+ def sigint_received?(exception)
+ resolve_exception_array(exception).any? do |e|
+ e.is_a? Chef::Exceptions::SigInt
+ end
+ end
+
+ def sigterm_received?(exception)
+ resolve_exception_array(exception).any? do |e|
+ e.is_a? Chef::Exceptions::SigTerm
+ end
+ end
+
+ def resolve_exception_array(exception)
+ exception_array = [exception]
+ if exception.respond_to?(:wrapped_errors)
+ exception.wrapped_errors.each do |e|
+ exception_array.push e
+ end
+ end
+ exception_array
+ end
+
+ def valid_exit_codes
+ VALID_RFC_062_EXIT_CODES.values
+ end
+
+ def notify_on_deprecation(message)
+ begin
+ Chef.log_deprecation(message)
+ rescue Chef::Exceptions::DeprecatedFeatureError
+ # Have to rescue this, otherwise this unhandled error preempts
+ # the current exit code assignment.
+ end
+ end
+
+ def deprecation_warning
+ "Chef RFC 062 (https://github.com/chef/chef-rfc/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?
+ return DEPRECATED_RFC_062_EXIT_CODES[:DEPRECATED_FAILURE]
+ else
+ VALID_RFC_062_EXIT_CODES[:GENERIC_FAILURE]
+ end
+ end
+
+ end
+ end
+
+ end
+end
diff --git a/lib/chef/application/solo.rb b/lib/chef/application/solo.rb
index bc2d279508..ecac3f4d4e 100644
--- a/lib/chef/application/solo.rb
+++ b/lib/chef/application/solo.rb
@@ -212,6 +212,7 @@ class Chef::Application::Solo < Chef::Application
def run
setup_signal_handlers
reconfigure
+ for_ezra if Chef::Config[:ez]
if !Chef::Config[:solo_legacy_mode]
Chef::Application::Client.new.run
else
@@ -232,6 +233,14 @@ class Chef::Application::Solo < Chef::Application
Chef::Log.deprecation("-r MUST be changed to --recipe-url, the -r option will be changed in Chef 13.0") if ARGV.include?("-r")
if !Chef::Config[:solo_legacy_mode]
+ # Because we re-parse ARGV when we move to chef-client, we need to tidy up some options first.
+ ARGV.delete("--ez")
+
+ # -r means something entirely different in chef-client land, so let's replace it with a "safe" value
+ if dash_r = ARGV.index("-r")
+ ARGV[dash_r] = "--recipe-url"
+ end
+
Chef::Config[:local_mode] = true
else
configure_legacy_mode!
@@ -277,7 +286,6 @@ class Chef::Application::Solo < Chef::Application
end
def run_application
- for_ezra if Chef::Config[:ez]
if !Chef::Config[:client_fork] || Chef::Config[:once]
# Run immediately without interval sleep or splay
begin
@@ -285,7 +293,7 @@ class Chef::Application::Solo < Chef::Application
rescue SystemExit
raise
rescue Exception => e
- Chef::Application.fatal!("#{e.class}: #{e.message}", 1)
+ Chef::Application.fatal!("#{e.class}: #{e.message}", e)
end
else
interval_run_chef_client
@@ -332,7 +340,7 @@ EOH
Chef::Log.debug("#{e.class}: #{e}\n#{e.backtrace.join("\n")}")
retry
else
- Chef::Application.fatal!("#{e.class}: #{e.message}", 1)
+ Chef::Application.fatal!("#{e.class}: #{e.message}", e)
end
end
end
diff --git a/lib/chef/application/windows_service.rb b/lib/chef/application/windows_service.rb
index fca1ed3689..2f1456ac45 100644
--- a/lib/chef/application/windows_service.rb
+++ b/lib/chef/application/windows_service.rb
@@ -319,11 +319,11 @@ class Chef
Chef::Config.merge!(config)
rescue SocketError
- Chef::Application.fatal!("Error getting config file #{Chef::Config[:config_file]}", 2)
+ Chef::Application.fatal!("Error getting config file #{Chef::Config[:config_file]}", Chef::Exceptions::DeprecatedExitCode.new)
rescue Chef::Exceptions::ConfigurationError => error
- Chef::Application.fatal!("Error processing config file #{Chef::Config[:config_file]} with error #{error.message}", 2)
+ Chef::Application.fatal!("Error processing config file #{Chef::Config[:config_file]} with error #{error.message}", Chef::Exceptions::DeprecatedExitCode.new)
rescue Exception => error
- Chef::Application.fatal!("Unknown error processing config file #{Chef::Config[:config_file]} with error #{error.message}", 2)
+ Chef::Application.fatal!("Unknown error processing config file #{Chef::Config[:config_file]} with error #{error.message}", Chef::Exceptions::DeprecatedExitCode.new)
end
end
diff --git a/lib/chef/client.rb b/lib/chef/client.rb
index 054b284bd5..c857da1b93 100644
--- a/lib/chef/client.rb
+++ b/lib/chef/client.rb
@@ -45,6 +45,7 @@ require "chef/formatters/doc"
require "chef/formatters/minimal"
require "chef/version"
require "chef/resource_reporter"
+require "chef/data_collector"
require "chef/audit/audit_reporter"
require "chef/run_lock"
require "chef/policy_builder"
@@ -263,6 +264,7 @@ class Chef
run_ohai
register unless Chef::Config[:solo_legacy_mode]
+ register_data_collector_reporter
load_node
@@ -957,6 +959,12 @@ class Chef
Chef::ReservedNames::Win32::Security.has_admin_privileges?
end
+
+ # Register the data collector reporter to send event information to the
+ # data collector server
+ def register_data_collector_reporter
+ events.register(Chef::DataCollector::Reporter.new) if Chef::DataCollector.register_reporter?
+ end
end
end
diff --git a/lib/chef/config_fetcher.rb b/lib/chef/config_fetcher.rb
index acd2f07f5e..ee1b64956a 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, 2)
+ Chef::Application.fatal!("Could not parse the provided JSON file (#{config_location}): " + error.message, Chef::Exceptions::DeprecatedExitCode.new)
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}", 2)
+ Chef::Application.fatal!("Cannot fetch config '#{config_location}': '#{error.class}: #{error.message}", Chef::Exceptions::DeprecatedExitCode.new)
end
def read_local_config
::File.read(config_location)
rescue Errno::ENOENT
- Chef::Application.fatal!("Cannot load configuration from #{config_location}", 2)
+ Chef::Application.fatal!("Cannot load configuration from #{config_location}", Chef::Exceptions::DeprecatedExitCode.new)
rescue Errno::EACCES
- Chef::Application.fatal!("Permissions are incorrect on #{config_location}. Please chmod a+r #{config_location}", 2)
+ Chef::Application.fatal!("Permissions are incorrect on #{config_location}. Please chmod a+r #{config_location}", Chef::Exceptions::DeprecatedExitCode.new)
end
def config_missing?
diff --git a/lib/chef/data_collector.rb b/lib/chef/data_collector.rb
new file mode 100644
index 0000000000..c8dd354f60
--- /dev/null
+++ b/lib/chef/data_collector.rb
@@ -0,0 +1,333 @@
+#
+# Author:: Adam Leff (<adamleff@chef.io>)
+# Author:: Ryan Cragun (<ryan@chef.io>)
+#
+# Copyright:: Copyright 2012-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 "uri"
+require "chef/event_dispatch/base"
+require "chef/data_collector/messages"
+require "chef/data_collector/resource_report"
+
+class Chef
+
+ # == Chef::DataCollector
+ # Provides methods for determinine whether a reporter should be registered.
+ class DataCollector
+ def self.register_reporter?
+ Chef::Config[:data_collector][:server_url] &&
+ !Chef::Config[:why_run] &&
+ self.reporter_enabled_for_current_mode?
+ end
+
+ def self.reporter_enabled_for_current_mode?
+ if Chef::Config[:solo] || Chef::Config[:local_mode]
+ acceptable_modes = [:solo, :both]
+ else
+ acceptable_modes = [:client, :both]
+ end
+
+ acceptable_modes.include?(Chef::Config[:data_collector][:mode])
+ end
+
+ # == Chef::DataCollector::Reporter
+ # Provides an event handler that can be registered to report on Chef
+ # run data. Unlike the existing Chef::ResourceReporter event handler,
+ # the DataCollector handler is not tied to a Chef Server / Chef Reporting
+ # and exports its data through a webhook-like mechanism to a configured
+ # endpoint.
+ class Reporter < EventDispatch::Base
+ attr_reader :completed_resources, :status, :exception, :error_descriptions,
+ :expanded_run_list, :run_status, :http,
+ :current_resource_report, :enabled
+
+ def initialize
+ @completed_resources = []
+ @current_resource_loaded = nil
+ @error_descriptions = {}
+ @expanded_run_list = {}
+ @http = Chef::HTTP.new(data_collector_server_url)
+ @enabled = true
+ end
+
+ # see EventDispatch::Base#run_started
+ # Upon receipt, we will send our run start message to the
+ # configured DataCollector endpoint. Depending on whether
+ # the user has configured raise_on_failure, if we cannot
+ # send the message, we will either disable the DataCollector
+ # Reporter for the duration of this run, or we'll raise an
+ # exception.
+ def run_started(current_run_status)
+ update_run_status(current_run_status)
+
+ disable_reporter_on_error do
+ send_to_data_collector(
+ Chef::DataCollector::Messages.run_start_message(current_run_status).to_json
+ )
+ end
+ end
+
+ # see EventDispatch::Base#run_completed
+ # Upon receipt, we will send our run completion message to the
+ # configured DataCollector endpoint.
+ def run_completed(node)
+ send_run_completion(status: "success")
+ end
+
+ # see EventDispatch::Base#run_failed
+ def run_failed(exception)
+ send_run_completion(status: "failure")
+ end
+
+ # see EventDispatch::Base#resource_current_state_loaded
+ # Create a new ResourceReport instance that we'll use to track
+ # the state of this resource during the run. Nested resources are
+ # ignored as they are assumed to be an inline resource of a custom
+ # resource, and we only care about tracking top-level resources.
+ def resource_current_state_loaded(new_resource, action, current_resource)
+ return if nested_resource?(new_resource)
+ update_current_resource_report(
+ Chef::DataCollector::ResourceReport.new(
+ new_resource,
+ action,
+ current_resource
+ )
+ )
+ end
+
+ # see EventDispatch::Base#resource_up_to_date
+ # Mark our ResourceReport status accordingly
+ def resource_up_to_date(new_resource, action)
+ current_resource_report.up_to_date unless nested_resource?(new_resource)
+ end
+
+ # see EventDispatch::Base#resource_skipped
+ # If this is a top-level resource, we create a ResourceReport instance
+ # (because a skipped resource does not trigger the
+ # resource_current_state_loaded event), and flag it as skipped.
+ def resource_skipped(new_resource, action, conditional)
+ return if nested_resource?(new_resource)
+
+ update_current_resource_report(
+ Chef::DataCollector::ResourceReport.new(
+ new_resource,
+ action
+ )
+ )
+ current_resource_report.skipped(conditional)
+ end
+
+ # see EventDispatch::Base#resource_updated
+ # Flag the current ResourceReport instance as updated (as long as it's
+ # a top-level resource).
+ def resource_updated(new_resource, action)
+ current_resource_report.updated unless nested_resource?(new_resource)
+ end
+
+ # see EventDispatch::Base#resource_failed
+ # Flag the current ResourceReport as failed and supply the exception as
+ # long as it's a top-level resource, and update the run error text
+ # with the proper Formatter.
+ def resource_failed(new_resource, action, exception)
+ current_resource_report.failed(exception) unless nested_resource?(new_resource)
+ update_error_description(
+ Formatters::ErrorMapper.resource_failed(
+ new_resource,
+ action,
+ exception
+ ).for_json
+ )
+ end
+
+ # see EventDispatch::Base#resource_completed
+ # Mark the ResourceReport instance as finished (for timing details)
+ # and add it to the list of resources encountered during this run.
+ # This marks the end of this resource during this run.
+ def resource_completed(new_resource)
+ if current_resource_report && !nested_resource?(new_resource)
+ current_resource_report.finish
+ add_completed_resource(current_resource_report)
+ update_current_resource_report(nil)
+ end
+ end
+
+ # see EventDispatch::Base#run_list_expanded
+ # The expanded run list is stored for later use by the run_completed
+ # event and message.
+ def run_list_expanded(run_list_expansion)
+ @expanded_run_list = run_list_expansion
+ end
+
+ # see EventDispatch::Base#run_list_expand_failed
+ # The run error text is updated with the output of the appropriate
+ # formatter.
+ def run_list_expand_failed(node, exception)
+ update_error_description(
+ Formatters::ErrorMapper.run_list_expand_failed(
+ node,
+ exception
+ ).for_json
+ )
+ end
+
+ # see EventDispatch::Base#cookbook_resolution_failed
+ # The run error text is updated with the output of the appropriate
+ # formatter.
+ def cookbook_resolution_failed(expanded_run_list, exception)
+ update_error_description(
+ Formatters::ErrorMapper.cookbook_resolution_failed(
+ expanded_run_list,
+ exception
+ ).for_json
+ )
+ end
+
+ # see EventDispatch::Base#cookbook_sync_failed
+ # The run error text is updated with the output of the appropriate
+ # formatter.
+ def cookbook_sync_failed(cookbooks, exception)
+ update_error_description(
+ Formatters::ErrorMapper.cookbook_sync_failed(
+ cookbooks,
+ exception
+ ).for_json
+ )
+ end
+
+ private
+
+ #
+ # Yields to the passed-in block (which is expected to be some interaction
+ # with the DataCollector endpoint). If some communication failure occurs,
+ # either disable any future communications to the DataCollector endpoint, or
+ # raise an exception (if the user has set
+ # Chef::Config.data_collector.raise_on_failure to true.)
+ #
+ # @param block [Proc] A ruby block to run. Ignored if a command is given.
+ #
+ def disable_reporter_on_error
+ yield
+ rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET,
+ Errno::ECONNREFUSED, EOFError, Net::HTTPBadResponse,
+ Net::HTTPHeaderSyntaxError, Net::ProtocolError, OpenSSL::SSL::SSLError => e
+ disable_data_collector_reporter
+ code = if e.respond_to?(:response) && e.response.code
+ e.response.code.to_s
+ else
+ "Exception Code Empty"
+ end
+
+ msg = "Error while reporting run start to Data Collector. " \
+ "URL: #{data_collector_server_url} " \
+ "Exception: #{code} -- #{e.message} "
+
+ if Chef::Config[:data_collector][:raise_on_failure]
+ Chef::Log.error(msg)
+ raise
+ else
+ Chef::Log.warn(msg)
+ end
+ end
+
+ def send_to_data_collector(message)
+ return unless data_collector_accessible?
+
+ Chef::Log.debug("data_collector_reporter: POSTing the following message to #{data_collector_server_url}: #{message}")
+ http.post(nil, message, headers)
+ end
+
+ #
+ # Send any messages to the DataCollector endpoint that are necessary to
+ # indicate the run has completed. Currently, two messages are sent:
+ #
+ # - An "action" message with the node object indicating it's been updated
+ # - An "run_converge" (i.e. RunEnd) message with details about the run,
+ # what resources were modified/up-to-date/skipped, etc.
+ #
+ # @param opts [Hash] Additional details about the run, such as its success/failure.
+ #
+ def send_run_completion(opts)
+ # If run_status is nil we probably failed before the client triggered
+ # the run_started callback. In this case we'll skip updating because
+ # we have nothing to report.
+ return unless run_status
+
+ send_to_data_collector(Chef::DataCollector::Messages.node_update_message(run_status).to_json)
+ send_to_data_collector(
+ Chef::DataCollector::Messages.run_end_message(
+ run_status: run_status,
+ expanded_run_list: expanded_run_list,
+ completed_resources: completed_resources,
+ status: opts[:status],
+ error_descriptions: error_descriptions
+ ).to_json
+ )
+ end
+
+ def headers
+ headers = { "Content-Type" => "application/json" }
+
+ unless data_collector_token.nil?
+ headers["x-data-collector-token"] = data_collector_token
+ headers["x-data-collector-auth"] = "version=1.0"
+ end
+
+ headers
+ end
+
+ def data_collector_server_url
+ Chef::Config[:data_collector][:server_url]
+ end
+
+ def data_collector_token
+ Chef::Config[:data_collector][:token]
+ end
+
+ def add_completed_resource(resource_report)
+ @completed_resources << resource_report
+ end
+
+ def disable_data_collector_reporter
+ @enabled = false
+ end
+
+ def data_collector_accessible?
+ @enabled
+ end
+
+ def update_run_status(run_status)
+ @run_status = run_status
+ end
+
+ def update_current_resource_report(resource_report)
+ @current_resource_report = resource_report
+ end
+
+ def update_error_description(discription_hash)
+ @error_descriptions = discription_hash
+ end
+
+ # If we are getting messages about a resource while we are in the middle of
+ # another resource's update, we assume that the nested resource is just the
+ # implementation of a provider, and we want to hide it from the reporting
+ # output.
+ def nested_resource?(new_resource)
+ @current_resource_report && @current_resource_report.new_resource != new_resource
+ end
+ end
+ end
+end
diff --git a/lib/chef/data_collector/messages.rb b/lib/chef/data_collector/messages.rb
new file mode 100644
index 0000000000..e23262c9e4
--- /dev/null
+++ b/lib/chef/data_collector/messages.rb
@@ -0,0 +1,125 @@
+#
+# Author:: Adam Leff (<adamleff@chef.io)
+# Author:: Ryan Cragun (<ryan@chef.io>)
+#
+# Copyright:: Copyright 2012-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 "json"
+require "securerandom"
+require_relative "messages/helpers"
+
+class Chef
+ class DataCollector
+ module Messages
+ extend Helpers
+
+ #
+ # Message payload that is sent to the DataCollector server at the
+ # start of a Chef run.
+ #
+ # @param run_status [Chef::RunStatus] The RunStatus instance for this node/run.
+ #
+ # @return [Hash] A hash containing the run start message data.
+ #
+ def self.run_start_message(run_status)
+ {
+ "chef_server_fqdn" => chef_server_fqdn(run_status),
+ "entity_uuid" => node_uuid,
+ "id" => run_status.run_id,
+ "message_version" => "1.0.0",
+ "message_type" => "run_start",
+ "node_name" => run_status.node.name,
+ "organization_name" => organization,
+ "run_id" => run_status.run_id,
+ "source" => collector_source,
+ "start_time" => run_status.start_time.utc.iso8601,
+ }
+ end
+
+ #
+ # Message payload that is sent to the DataCollector server at the
+ # end of a Chef run.
+ #
+ # @param reporter_data [Hash] Data supplied by the Reporter, such as run_status, resource counts, etc.
+ #
+ # @return [Hash] A hash containing the run end message data.
+ #
+ def self.run_end_message(reporter_data)
+ run_status = reporter_data[:run_status]
+
+ message = {
+ "chef_server_fqdn" => chef_server_fqdn(run_status),
+ "entity_uuid" => node_uuid,
+ "expanded_run_list" => reporter_data[:expanded_run_list],
+ "id" => run_status.run_id,
+ "message_version" => "1.0.0",
+ "message_type" => "run_converge",
+ "node_name" => run_status.node.name,
+ "organization_name" => organization,
+ "resources" => reporter_data[:completed_resources].map(&:for_json),
+ "run_id" => run_status.run_id,
+ "run_list" => run_status.node.run_list.for_json,
+ "start_time" => run_status.start_time.utc.iso8601,
+ "end_time" => run_status.end_time.utc.iso8601,
+ "source" => collector_source,
+ "status" => reporter_data[:status],
+ "total_resource_count" => reporter_data[:completed_resources].count,
+ "updated_resource_count" => reporter_data[:completed_resources].select { |r| r.status == "updated" }.count,
+ }
+
+ message["error"] = {
+ "class" => run_status.exception.class,
+ "message" => run_status.exception.message,
+ "backtrace" => run_status.exception.backtrace,
+ "description" => reporter_data[:error_descriptions],
+ } if run_status.exception
+
+ message
+ end
+
+ #
+ # Message payload that is sent to the DataCollector server at the
+ # end of a Chef run.
+ #
+ # @param run_status [Chef::RunStatus] The RunStatus instance for this node/run.
+ #
+ # @return [Hash] A hash containing the node object and related metadata.
+ #
+ def self.node_update_message(run_status)
+ {
+ "entity_name" => run_status.node.name,
+ "entity_type" => "node",
+ "entity_uuid" => node_uuid,
+ "id" => SecureRandom.uuid,
+ "message_version" => "1.1.0",
+ "message_type" => "action",
+ "organization_name" => organization,
+ "recorded_at" => Time.now.utc.iso8601,
+ "remote_hostname" => run_status.node["fqdn"],
+ "requestor_name" => run_status.node.name,
+ "requestor_type" => "client",
+ "run_id" => run_status.run_id,
+ "service_hostname" => chef_server_fqdn(run_status),
+ "source" => collector_source,
+ "task" => "update",
+ "user_agent" => Chef::HTTP::HTTPRequest::DEFAULT_UA,
+ "data" => run_status.node,
+ }
+ end
+ end
+ end
+end
diff --git a/lib/chef/data_collector/messages/helpers.rb b/lib/chef/data_collector/messages/helpers.rb
new file mode 100644
index 0000000000..3e52f80047
--- /dev/null
+++ b/lib/chef/data_collector/messages/helpers.rb
@@ -0,0 +1,161 @@
+#
+# Author:: Adam Leff (<adamleff@chef.io)
+# Author:: Ryan Cragun (<ryan@chef.io>)
+#
+# Copyright:: Copyright 2012-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
+ class DataCollector
+ module Messages
+ module Helpers
+ #
+ # Fully-qualified domain name of the Chef Server configured in Chef::Config
+ # If the chef_server_url cannot be parsed as a URI, the node["fqdn"] attribute
+ # will be returned, or "localhost" if the run_status is unavailable to us.
+ #
+ # @param run_status [Chef::RunStatus] The RunStatus object for this Chef Run.
+ #
+ # @return [String] FQDN of the configured Chef Server, or node/localhost if not found.
+ #
+ def chef_server_fqdn(run_status)
+ if !Chef::Config[:chef_server_url].nil?
+ URI(Chef::Config[:chef_server_url]).host
+ elsif !Chef::Config[:node_name].nil?
+ Chef::Config[:node_name]
+ else
+ "localhost"
+ end
+ end
+
+ #
+ # The organization name the node is associated with. For Chef Solo runs, a
+ # user-configured organization string is returned, or the string "chef_solo"
+ # if such a string is not configured.
+ #
+ # @return [String] Organization to which the node is associated
+ #
+ def organization
+ solo_run? ? data_collector_organization : chef_server_organization
+ end
+
+ #
+ # Returns the user-configured organization, or "chef_solo" if none is configured.
+ #
+ # This is only used when Chef is run in Solo mode.
+ #
+ # @return [String] Data-collector-specific organization used when running in Chef Solo
+ #
+ def data_collector_organization
+ Chef::Config[:data_collector][:organization] || "chef_solo"
+ end
+
+ #
+ # Return the organization assumed by the configured chef_server_url.
+ #
+ # We must parse this from the Chef::Config[:chef_server_url] because a node
+ # has no knowledge of an organization or to which organization is belongs.
+ #
+ # If we cannot determine the organization, we return "unknown_organization"
+ #
+ # @return [String] shortname of the Chef Server organization
+ #
+ def chef_server_organization
+ return "unknown_organization" unless Chef::Config[:chef_server_url]
+
+ Chef::Config[:chef_server_url].match(%r{/+organizations/+(\w+)}).nil? ? "unknown_organization" : $1
+ end
+
+ #
+ # The source of the data collecting during this run, used by the
+ # DataCollector endpoint to determine if Chef was in Solo mode or not.
+ #
+ # @return [String] "chef_solo" if in Solo mode, "chef_client" if in Client mode
+ #
+ def collector_source
+ solo_run? ? "chef_solo" : "chef_client"
+ end
+
+ #
+ # If we're running in Solo (legacy) mode, or in Solo (formerly
+ # "Chef Client Local Mode"), we're considered to be in a "solo run".
+ #
+ # @return [Boolean] Whether we're in a solo run or not
+ #
+ def solo_run?
+ Chef::Config[:solo] || Chef::Config[:local_mode]
+ end
+
+ #
+ # Returns a UUID that uniquely identifies this node for reporting reasons.
+ #
+ # The node is read in from disk if it exists, or it's generated if it does
+ # does not exist.
+ #
+ # @return [String] UUID for the node
+ #
+ def node_uuid
+ read_node_uuid || generate_node_uuid
+ end
+
+ #
+ # Generates a UUID for the node via SecureRandom.uuid and writes out
+ # metadata file so the UUID persists between runs.
+ #
+ # @return [String] UUID for the node
+ #
+ def generate_node_uuid
+ uuid = SecureRandom.uuid
+ update_metadata("node_uuid", uuid)
+
+ uuid
+ end
+
+ #
+ # Reads in the node UUID from the node metadata file
+ #
+ # @return [String] UUID for the node
+ #
+ def read_node_uuid
+ metadata["node_uuid"]
+ end
+
+ #
+ # Returns the DataCollector metadata for this node
+ #
+ # If the metadata file does not exist in the file cache path,
+ # an empty hash will be returned.
+ #
+ # @return [Hash] DataCollector metadata for this node
+ #
+ def metadata
+ JSON.load(Chef::FileCache.load(metadata_filename))
+ rescue Chef::Exceptions::FileNotFound
+ {}
+ end
+
+ def update_metadata(key, value)
+ metadata[key] = value
+ Chef::FileCache.store(metadata_filename, metadata.to_json, 0644)
+ end
+
+ def metadata_filename
+ "data_collector_metadata.json"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/data_collector/resource_report.rb b/lib/chef/data_collector/resource_report.rb
new file mode 100644
index 0000000000..1793fe2c9d
--- /dev/null
+++ b/lib/chef/data_collector/resource_report.rb
@@ -0,0 +1,84 @@
+#
+# Author:: Adam Leff (<adamleff@chef.io>)
+# Author:: Ryan Cragun (<ryan@chef.io>)
+#
+# Copyright:: Copyright 2012-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
+ class DataCollector
+ class ResourceReport
+
+ attr_reader :action, :current_resource, :elapsed_time, :new_resource, :status
+ attr_accessor :conditional, :exception
+
+ def initialize(new_resource, action, current_resource = nil)
+ @new_resource = new_resource
+ @action = action
+ @current_resource = current_resource
+ end
+
+ def skipped(conditional)
+ @status = "skipped"
+ @conditional = conditional
+ end
+
+ def updated
+ @status = "updated"
+ end
+
+ def failed(exception)
+ @current_resource = nil
+ @status = "failed"
+ @exception = exception
+ end
+
+ def up_to_date
+ @status = "up-to-date"
+ end
+
+ def finish
+ @elapsed_time = new_resource.elapsed_time
+ end
+
+ def to_hash
+ hash = {
+ "type" => new_resource.resource_name.to_sym,
+ "name" => new_resource.name.to_s,
+ "id" => new_resource.identity.to_s,
+ "after" => new_resource.state_for_resource_reporter,
+ "before" => current_resource ? current_resource.state_for_resource_reporter : {},
+ "duration" => (elapsed_time * 1000).to_i.to_s,
+ "delta" => new_resource.respond_to?(:diff) ? new_resource.diff : "",
+ "result" => action.to_s,
+ "status" => status,
+ }
+
+ if new_resource.cookbook_name
+ hash["cookbook_name"] = new_resource.cookbook_name
+ hash["cookbook_version"] = new_resource.cookbook_version.version
+ end
+
+ hash["conditional"] = conditional.to_text if status == "skipped"
+ hash["error_message"] = exception.message unless exception.nil?
+
+ hash
+ end
+ alias :to_h :to_hash
+ alias :for_json :to_hash
+ end
+ end
+end
diff --git a/lib/chef/dsl/platform_introspection.rb b/lib/chef/dsl/platform_introspection.rb
index 276a03af63..a0c2d33967 100644
--- a/lib/chef/dsl/platform_introspection.rb
+++ b/lib/chef/dsl/platform_introspection.rb
@@ -245,6 +245,22 @@ class Chef
end
end
+ # Shamelessly stolen from https://github.com/sethvargo/chef-sugar/blob/master/lib/chef/sugar/docker.rb
+ # Given a node object, returns whether the node is a docker container.
+ #
+ # === Parameters
+ # node:: [Chef::Node] The node to check.
+ #
+ # === Returns
+ # true:: if the current node is a docker container
+ # false:: if the current node is not a docker container
+ def docker?(node = run_context.nil? ? nil : run_context.node)
+ # Using "File.exist?('/.dockerinit') || File.exist?('/.dockerenv')" makes Travis sad,
+ # and that makes us sad too.
+ node && node[:virtualization] && node[:virtualization][:systems] &&
+ node[:virtualization][:systems][:docker] && node[:virtualization][:systems][:docker] == "guest"
+ end
+
end
end
end
diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb
index 6afcc9c51e..ea90d80cd8 100644
--- a/lib/chef/exceptions.rb
+++ b/lib/chef/exceptions.rb
@@ -42,6 +42,8 @@ class Chef
end
class Application < RuntimeError; end
+ class SigInt < RuntimeError; end
+ class SigTerm < RuntimeError; end
class Cron < RuntimeError; end
class Env < RuntimeError; end
class Exec < RuntimeError; end
@@ -56,6 +58,14 @@ 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"
@@ -66,6 +76,9 @@ class Chef
class Group < RuntimeError; end
class Link < RuntimeError; end
class Mount < RuntimeError; end
+ class Reboot < Exception; end
+ class RebootPending < Exception; end
+ class RebootFailed < Mixlib::ShellOut::ShellCommandFailed; end
class PrivateKeyMissing < RuntimeError; end
class CannotWritePrivateKey < RuntimeError; end
class RoleNotFound < RuntimeError; end
@@ -426,18 +439,20 @@ This error is most often caused by network issues (proxies, etc) outside of chef
end
end
- class AuditControlGroupDuplicate < RuntimeError
+ class AuditError < RuntimeError; end
+
+ class AuditControlGroupDuplicate < AuditError
def initialize(name)
super "Control group with name '#{name}' has already been defined"
end
end
- class AuditNameMissing < RuntimeError; end
- class NoAuditsProvided < RuntimeError
+ class AuditNameMissing < AuditError; end
+ class NoAuditsProvided < AuditError
def initialize
super "You must provide a block with controls"
end
end
- class AuditsFailed < RuntimeError
+ class AuditsFailed < AuditError
def initialize(num_failed, num_total)
super "Audit phase found failures - #{num_failed}/#{num_total} controls failed"
end
diff --git a/lib/chef/platform/rebooter.rb b/lib/chef/platform/rebooter.rb
index c678b60dd1..74c8b2da1f 100644
--- a/lib/chef/platform/rebooter.rb
+++ b/lib/chef/platform/rebooter.rb
@@ -19,6 +19,7 @@
require "chef/dsl/reboot_pending"
require "chef/log"
require "chef/platform"
+require "chef/application/exit_code"
class Chef
class Platform
@@ -27,6 +28,8 @@ class Chef
class << self
+ include Chef::DSL::RebootPending
+
def reboot!(node)
reboot_info = node.run_context.reboot_info
@@ -38,8 +41,16 @@ class Chef
"shutdown -r +#{reboot_info[:delay_mins]} \"#{reboot_info[:reason]}\""
end
- Chef::Log.warn "Rebooting server at a recipe's request. Details: #{reboot_info.inspect}"
- shell_out!(cmd)
+ msg = "Rebooting server at a recipe's request. Details: #{reboot_info.inspect}"
+ begin
+ Chef::Log.warn msg
+ shell_out!(cmd)
+ rescue Mixlib::ShellOut::ShellCommandFailed => e
+ 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
end
# this is a wrapper function so Chef::Client only needs a single line of code.
diff --git a/lib/chef/provider/directory.rb b/lib/chef/provider/directory.rb
index 7cc05259b6..619ab5d8b6 100644
--- a/lib/chef/provider/directory.rb
+++ b/lib/chef/provider/directory.rb
@@ -145,7 +145,9 @@ class Chef
if ::File.exists?(@new_resource.path)
converge_by("delete existing directory #{@new_resource.path}") do
if @new_resource.recursive == true
- FileUtils.rm_rf(@new_resource.path)
+ # we don't use rm_rf here because it masks all errors, including
+ # IO errors or permission errors that would prvent the deletion
+ FileUtils.rm_r(@new_resource.path)
Chef::Log.info("#{@new_resource} deleted #{@new_resource.path} recursively")
else
::Dir.delete(@new_resource.path)
diff --git a/lib/chef/provider/package/chocolatey.rb b/lib/chef/provider/package/chocolatey.rb
index ebd3f987cd..36bc170590 100644
--- a/lib/chef/provider/package/chocolatey.rb
+++ b/lib/chef/provider/package/chocolatey.rb
@@ -229,13 +229,11 @@ EOS
def available_packages
@available_packages ||=
begin
- cmd = [ "list -ar #{package_name_array.join ' '}" ]
+ cmd = [ "list -r #{package_name_array.join ' '}" ]
cmd.push( "-source #{new_resource.source}" ) if new_resource.source
- parse_list_output(*cmd).each_with_object({}) do |name_version, available|
- name, version = name_version
- if desired_name_versions[name].nil? || desired_name_versions[name] == version
- available[name] = version
- end
+ raw = parse_list_output(*cmd)
+ raw.keys.each_with_object({}) do |name, available|
+ available[name] = desired_name_versions[name] || raw[name]
end
end
end
@@ -252,15 +250,15 @@ EOS
# (names are downcased for case-insenstive matching)
#
# @param cmd [String] command to run
- # @return [Array] list output converted to ruby Hash
+ # @return [Hash] list output converted to ruby Hash
def parse_list_output(*args)
- list = []
+ hash = {}
choco_command(*args).stdout.each_line do |line|
next if line.start_with?("Chocolatey v")
name, version = line.split("|")
- list << [ name.downcase, version.chomp ]
+ hash[name.downcase] = version.chomp
end
- list
+ hash
end
# Helper to downcase all names in an array
diff --git a/lib/chef/provider/package/portage.rb b/lib/chef/provider/package/portage.rb
index 7c52e43bff..52b46b04b4 100644
--- a/lib/chef/provider/package/portage.rb
+++ b/lib/chef/provider/package/portage.rb
@@ -41,7 +41,7 @@ class Chef
globsafe_pkg = Chef::Util::PathHelper.escape_glob_dir(pkg)
possibilities = Dir["/var/db/pkg/#{globsafe_category || "*"}/#{globsafe_pkg}-*"].map { |d| d.sub(%r{/var/db/pkg/}, "") }
versions = possibilities.map do |entry|
- if entry =~ %r{[^/]+/#{Regexp.escape(pkg)}\-(\d[\.\d]*((_(alpha|beta|pre|rc|p)\d*)*)?(-r\d+)?)}
+ if entry =~ %r{[^/]+/#{Regexp.escape(pkg)}\-(\d[\.\d]*[a-z]?((_(alpha|beta|pre|rc|p)\d*)*)?(-r\d+)?)}
[$&, $1]
end
end.compact
diff --git a/lib/chef/provider/package/yum.rb b/lib/chef/provider/package/yum.rb
index 64f1b2043c..74d52946f7 100644
--- a/lib/chef/provider/package/yum.rb
+++ b/lib/chef/provider/package/yum.rb
@@ -18,10 +18,10 @@
require "chef/config"
require "chef/provider/package"
-require "chef/mixin/which"
-require "chef/resource/package"
-require "singleton"
+require "chef/resource/yum_package"
require "chef/mixin/get_source_from_package"
+require "chef/provider/package/yum/rpm_utils"
+require "chef/provider/package/yum/yum_cache"
class Chef
class Provider
@@ -31,961 +31,6 @@ class Chef
provides :package, platform_family: %w{rhel fedora}
provides :yum_package, os: "linux"
- class RPMUtils
- class << self
-
- # RPM::Version version_parse equivalent
- def version_parse(evr)
- return if evr.nil?
-
- epoch = nil
- # assume this is a version
- version = evr
- release = nil
-
- lead = 0
- tail = evr.size
-
- if %r{^([\d]+):}.match(evr) # rubocop:disable Performance/RedundantMatch
- epoch = $1.to_i
- lead = $1.length + 1
- elsif evr[0].ord == ":".ord
- epoch = 0
- lead = 1
- end
-
- if %r{:?.*-(.*)$}.match(evr) # rubocop:disable Performance/RedundantMatch
- release = $1
- tail = evr.length - release.length - lead - 1
-
- if release.empty?
- release = nil
- end
- end
-
- version = evr[lead, tail]
- if version.empty?
- version = nil
- end
-
- [ epoch, version, release ]
- end
-
- # verify
- def isalnum(x)
- isalpha(x) || isdigit(x)
- end
-
- def isalpha(x)
- v = x.ord
- (v >= 65 && v <= 90) || (v >= 97 && v <= 122)
- end
-
- def isdigit(x)
- v = x.ord
- v >= 48 && v <= 57
- end
-
- # based on the reference spec in lib/rpmvercmp.c in rpm 4.9.0
- def rpmvercmp(x, y)
- # easy! :)
- return 0 if x == y
-
- if x.nil?
- x = ""
- end
-
- if y.nil?
- y = ""
- end
-
- # not so easy :(
- #
- # takes 2 strings like
- #
- # x = "1.20.b18.el5"
- # y = "1.20.b17.el5"
- #
- # breaks into purely alpha and numeric segments and compares them using
- # some rules
- #
- # * 10 > 1
- # * 1 > a
- # * z > a
- # * Z > A
- # * z > Z
- # * leading zeros are ignored
- # * separators (periods, commas) are ignored
- # * "1.20.b18.el5.extrastuff" > "1.20.b18.el5"
-
- x_pos = 0 # overall string element reference position
- x_pos_max = x.length - 1 # number of elements in string, starting from 0
- x_seg_pos = 0 # segment string element reference position
- x_comp = nil # segment to compare
-
- y_pos = 0
- y_seg_pos = 0
- y_pos_max = y.length - 1
- y_comp = nil
-
- while x_pos <= x_pos_max && y_pos <= y_pos_max
- # first we skip over anything non alphanumeric
- while (x_pos <= x_pos_max) && (isalnum(x[x_pos]) == false)
- x_pos += 1 # +1 over pos_max if end of string
- end
- while (y_pos <= y_pos_max) && (isalnum(y[y_pos]) == false)
- y_pos += 1
- end
-
- # if we hit the end of either we are done matching segments
- if (x_pos == x_pos_max + 1) || (y_pos == y_pos_max + 1)
- break
- end
-
- # we are now at the start of a alpha or numeric segment
- x_seg_pos = x_pos
- y_seg_pos = y_pos
-
- # grab segment so we can compare them
- if isdigit(x[x_seg_pos].ord)
- x_seg_is_num = true
-
- # already know it's a digit
- x_seg_pos += 1
-
- # gather up our digits
- while (x_seg_pos <= x_pos_max) && isdigit(x[x_seg_pos])
- x_seg_pos += 1
- end
- # copy the segment but not the unmatched character that x_seg_pos will
- # refer to
- x_comp = x[x_pos, x_seg_pos - x_pos]
-
- while (y_seg_pos <= y_pos_max) && isdigit(y[y_seg_pos])
- y_seg_pos += 1
- end
- y_comp = y[y_pos, y_seg_pos - y_pos]
- else
- # we are comparing strings
- x_seg_is_num = false
-
- while (x_seg_pos <= x_pos_max) && isalpha(x[x_seg_pos])
- x_seg_pos += 1
- end
- x_comp = x[x_pos, x_seg_pos - x_pos]
-
- while (y_seg_pos <= y_pos_max) && isalpha(y[y_seg_pos])
- y_seg_pos += 1
- end
- y_comp = y[y_pos, y_seg_pos - y_pos]
- end
-
- # if y_seg_pos didn't advance in the above loop it means the segments are
- # different types
- if y_pos == y_seg_pos
- # numbers always win over letters
- return x_seg_is_num ? 1 : -1
- end
-
- # move the ball forward before we mess with the segments
- x_pos += x_comp.length # +1 over pos_max if end of string
- y_pos += y_comp.length
-
- # we are comparing numbers - simply convert them
- if x_seg_is_num
- x_comp = x_comp.to_i
- y_comp = y_comp.to_i
- end
-
- # compares ints or strings
- # don't return if equal - try the next segment
- if x_comp > y_comp
- return 1
- elsif x_comp < y_comp
- return -1
- end
-
- # if we've reached here than the segments are the same - try again
- end
-
- # we must have reached the end of one or both of the strings and they
- # matched up until this point
-
- # segments matched completely but the segment separators were different -
- # rpm reference code treats these as equal.
- if (x_pos == x_pos_max + 1) && (y_pos == y_pos_max + 1)
- return 0
- end
-
- # the most unprocessed characters left wins
- if (x_pos_max - x_pos) > (y_pos_max - y_pos)
- return 1
- else
- return -1
- end
- end
-
- end # self
- end # RPMUtils
-
- class RPMVersion
- include Comparable
-
- def initialize(*args)
- if args.size == 1
- @e, @v, @r = RPMUtils.version_parse(args[0])
- elsif args.size == 3
- @e = args[0].to_i
- @v = args[1]
- @r = args[2]
- else
- raise ArgumentError, "Expecting either 'epoch-version-release' or 'epoch, " +
- "version, release'"
- end
- end
- attr_reader :e, :v, :r
- alias :epoch :e
- alias :version :v
- alias :release :r
-
- def self.parse(*args)
- self.new(*args)
- end
-
- def <=>(y)
- compare_versions(y)
- end
-
- def compare(y)
- compare_versions(y, false)
- end
-
- def partial_compare(y)
- compare_versions(y, true)
- end
-
- # RPM::Version rpm_version_to_s equivalent
- def to_s
- if @r.nil?
- @v
- else
- "#{@v}-#{@r}"
- end
- end
-
- def evr
- "#{@e}:#{@v}-#{@r}"
- end
-
- private
-
- # Rough RPM::Version rpm_version_cmp equivalent - except much slower :)
- #
- # partial lets epoch and version segment equality be good enough to return equal, eg:
- #
- # 2:1.2-1 == 2:1.2
- # 2:1.2-1 == 2:
- #
- def compare_versions(y, partial = false)
- x = self
-
- # compare epoch
- if (x.e.nil? == false && x.e > 0) && y.e.nil?
- return 1
- elsif x.e.nil? && (y.e.nil? == false && y.e > 0)
- return -1
- elsif x.e.nil? == false && y.e.nil? == false
- if x.e < y.e
- return -1
- elsif x.e > y.e
- return 1
- end
- end
-
- # compare version
- if partial && (x.v.nil? || y.v.nil?)
- return 0
- elsif x.v.nil? == false && y.v.nil?
- return 1
- elsif x.v.nil? && y.v.nil? == false
- return -1
- elsif x.v.nil? == false && y.v.nil? == false
- cmp = RPMUtils.rpmvercmp(x.v, y.v)
- return cmp if cmp != 0
- end
-
- # compare release
- if partial && (x.r.nil? || y.r.nil?)
- return 0
- elsif x.r.nil? == false && y.r.nil?
- return 1
- elsif x.r.nil? && y.r.nil? == false
- return -1
- elsif x.r.nil? == false && y.r.nil? == false
- cmp = RPMUtils.rpmvercmp(x.r, y.r)
- return cmp
- end
-
- return 0
- end
- end
-
- class RPMPackage
- include Comparable
-
- def initialize(*args)
- if args.size == 4
- @n = args[0]
- @version = RPMVersion.new(args[1])
- @a = args[2]
- @provides = args[3]
- elsif args.size == 6
- @n = args[0]
- e = args[1].to_i
- v = args[2]
- r = args[3]
- @version = RPMVersion.new(e, v, r)
- @a = args[4]
- @provides = args[5]
- else
- raise ArgumentError, "Expecting either 'name, epoch-version-release, arch, provides' " +
- "or 'name, epoch, version, release, arch, provides'"
- end
-
- # We always have one, ourselves!
- if @provides.empty?
- @provides = [ RPMProvide.new(@n, @version.evr, :==) ]
- end
- end
- attr_reader :n, :a, :version, :provides
- alias :name :n
- alias :arch :a
-
- def <=>(y)
- compare(y)
- end
-
- def compare(y)
- x = self
-
- # easy! :)
- return 0 if x.nevra == y.nevra
-
- # compare name
- if x.n.nil? == false && y.n.nil?
- return 1
- elsif x.n.nil? && y.n.nil? == false
- return -1
- elsif x.n.nil? == false && y.n.nil? == false
- if x.n < y.n
- return -1
- elsif x.n > y.n
- return 1
- end
- end
-
- # compare version
- if x.version > y.version
- return 1
- elsif x.version < y.version
- return -1
- end
-
- # compare arch
- if x.a.nil? == false && y.a.nil?
- return 1
- elsif x.a.nil? && y.a.nil? == false
- return -1
- elsif x.a.nil? == false && y.a.nil? == false
- if x.a < y.a
- return -1
- elsif x.a > y.a
- return 1
- end
- end
-
- return 0
- end
-
- def to_s
- nevra
- end
-
- def nevra
- "#{@n}-#{@version.evr}.#{@a}"
- end
- end
-
- # Simple implementation from rpm and ruby-rpm reference code
- class RPMDependency
- def initialize(*args)
- if args.size == 3
- @name = args[0]
- @version = RPMVersion.new(args[1])
- # Our requirement to other dependencies
- @flag = args[2] || :==
- elsif args.size == 5
- @name = args[0]
- e = args[1].to_i
- v = args[2]
- r = args[3]
- @version = RPMVersion.new(e, v, r)
- @flag = args[4] || :==
- else
- raise ArgumentError, "Expecting either 'name, epoch-version-release, flag' or " +
- "'name, epoch, version, release, flag'"
- end
- end
- attr_reader :name, :version, :flag
-
- # Parses 2 forms:
- #
- # "mtr >= 2:0.71-3.0"
- # "mta"
- def self.parse(string)
- if %r{^(\S+)\s+(>|>=|=|==|<=|<)\s+(\S+)$}.match(string) # rubocop:disable Performance/RedundantMatch
- name = $1
- if $2 == "="
- flag = :==
- else
- flag = :"#{$2}"
- end
- version = $3
-
- return self.new(name, version, flag)
- else
- name = string
- return self.new(name, nil, nil)
- end
- end
-
- # Test if another RPMDependency satisfies our requirements
- def satisfy?(y)
- unless y.kind_of?(RPMDependency)
- raise ArgumentError, "Expecting an RPMDependency object"
- end
-
- x = self
-
- # Easy!
- if x.name != y.name
- return false
- end
-
- # Partial compare
- #
- # eg: x.version 2.3 == y.version 2.3-1
- sense = x.version.partial_compare(y.version)
-
- # Thanks to rpmdsCompare() rpmds.c
- if (sense < 0) && ((x.flag == :> || x.flag == :>=) || (y.flag == :<= || y.flag == :<))
- return true
- elsif (sense > 0) && ((x.flag == :< || x.flag == :<=) || (y.flag == :>= || y.flag == :>))
- return true
- elsif sense == 0 && (
- ((x.flag == :== || x.flag == :<= || x.flag == :>=) && (y.flag == :== || y.flag == :<= || y.flag == :>=)) ||
- (x.flag == :< && y.flag == :<) ||
- (x.flag == :> && y.flag == :>)
- )
- return true
- end
-
- return false
- end
- end
-
- class RPMProvide < RPMDependency; end
- class RPMRequire < RPMDependency; end
-
- class RPMDbPackage < RPMPackage
- # <rpm parts>, installed, available
- def initialize(*args)
- @repoid = args.pop
- # state
- @available = args.pop
- @installed = args.pop
- super(*args)
- end
- attr_reader :repoid, :available, :installed
- end
-
- # Simple storage for RPMPackage objects - keeps them unique and sorted
- class RPMDb
- def initialize
- # package name => [ RPMPackage, RPMPackage ] of different versions
- @rpms = Hash.new
- # package nevra => RPMPackage for lookups
- @index = Hash.new
- # provide name (aka feature) => [RPMPackage, RPMPackage] each providing this feature
- @provides = Hash.new
- # RPMPackages listed as available
- @available = Set.new
- # RPMPackages listed as installed
- @installed = Set.new
- end
-
- def [](package_name)
- self.lookup(package_name)
- end
-
- # Lookup package_name and return a descending array of package objects
- def lookup(package_name)
- pkgs = @rpms[package_name]
- if pkgs
- return pkgs.sort.reverse
- else
- return nil
- end
- end
-
- def lookup_provides(provide_name)
- @provides[provide_name]
- end
-
- # Using the package name as a key, and nevra for an index, keep a unique list of packages.
- # The available/installed state can be overwritten for existing packages.
- def push(*args)
- args.flatten.each do |new_rpm|
- unless new_rpm.kind_of?(RPMDbPackage)
- raise ArgumentError, "Expecting an RPMDbPackage object"
- end
-
- @rpms[new_rpm.n] ||= Array.new
-
- # we may already have this one, like when the installed list is refreshed
- idx = @index[new_rpm.nevra]
- if idx
- # grab the existing package if it's not
- curr_rpm = idx
- else
- @rpms[new_rpm.n] << new_rpm
-
- new_rpm.provides.each do |provide|
- @provides[provide.name] ||= Array.new
- @provides[provide.name] << new_rpm
- end
-
- curr_rpm = new_rpm
- end
-
- # Track the nevra -> RPMPackage association to avoid having to compare versions
- # with @rpms[new_rpm.n] on the next round
- @index[new_rpm.nevra] = curr_rpm
-
- # these are overwritten for existing packages
- if new_rpm.available
- @available << curr_rpm
- end
- if new_rpm.installed
- @installed << curr_rpm
- end
- end
- end
-
- def <<(*args)
- self.push(args)
- end
-
- def clear
- @rpms.clear
- @index.clear
- @provides.clear
- clear_available
- clear_installed
- end
-
- def clear_available
- @available.clear
- end
-
- def clear_installed
- @installed.clear
- end
-
- def size
- @rpms.size
- end
- alias :length :size
-
- def available_size
- @available.size
- end
-
- def installed_size
- @installed.size
- end
-
- def available?(package)
- @available.include?(package)
- end
-
- def installed?(package)
- @installed.include?(package)
- end
-
- def whatprovides(rpmdep)
- unless rpmdep.kind_of?(RPMDependency)
- raise ArgumentError, "Expecting an RPMDependency object"
- end
-
- what = []
-
- packages = lookup_provides(rpmdep.name)
- if packages
- packages.each do |pkg|
- pkg.provides.each do |provide|
- if provide.satisfy?(rpmdep)
- what << pkg
- end
- end
- end
- end
-
- return what
- end
- end
-
- # Cache for our installed and available packages, pulled in from yum-dump.py
- class YumCache
- include Chef::Mixin::Which
- include Chef::Mixin::ShellOut
- include Singleton
-
- attr_accessor :yum_binary
-
- def initialize
- @rpmdb = RPMDb.new
-
- # Next time @rpmdb is accessed:
- # :all - Trigger a run of "yum-dump.py --options --installed-provides", updates
- # yum's cache and parses options from /etc/yum.conf. Pulls in Provides
- # dependency data for installed packages only - this data is slow to
- # gather.
- # :provides - Same as :all but pulls in Provides data for available packages as well.
- # Used as a last resort when we can't find a Provides match.
- # :installed - Trigger a run of "yum-dump.py --installed", only reads the local rpm
- # db. Used between client runs for a quick refresh.
- # :none - Do nothing, a call to one of the reload methods is required.
- @next_refresh = :all
-
- @allow_multi_install = []
-
- @extra_repo_control = nil
-
- # these are for subsequent runs if we are on an interval
- Chef::Client.when_run_starts do
- YumCache.instance.reload
- end
- end
-
- attr_reader :extra_repo_control
-
- # Cache management
- #
-
- def refresh
- case @next_refresh
- when :none
- return nil
- when :installed
- reset_installed
- # fast
- opts = " --installed"
- when :all
- reset
- # medium
- opts = " --options --installed-provides"
- when :provides
- reset
- # slow!
- opts = " --options --all-provides"
- else
- raise ArgumentError, "Unexpected value in next_refresh: #{@next_refresh}"
- end
-
- if @extra_repo_control
- opts << " #{@extra_repo_control}"
- end
-
- opts << " --yum-lock-timeout #{Chef::Config[:yum_lock_timeout]}"
-
- one_line = false
- error = nil
-
- helper = ::File.join(::File.dirname(__FILE__), "yum-dump.py")
- status = nil
-
- begin
- status = shell_out!("#{python_bin} #{helper}#{opts}", :timeout => Chef::Config[:yum_timeout])
- status.stdout.each_line do |line|
- one_line = true
-
- line.chomp!
- if line =~ %r{\[option (.*)\] (.*)}
- if $1 == "installonlypkgs"
- @allow_multi_install = $2.split
- else
- raise Chef::Exceptions::Package, "Strange, unknown option line '#{line}' from yum-dump.py"
- end
- next
- end
-
- if line =~ %r{^(\S+) ([0-9]+) (\S+) (\S+) (\S+) \[(.*)\] ([i,a,r]) (\S+)$}
- name = $1
- epoch = $2
- version = $3
- release = $4
- arch = $5
- provides = parse_provides($6)
- type = $7
- repoid = $8
- else
- Chef::Log.warn("Problem parsing line '#{line}' from yum-dump.py! " +
- "Please check your yum configuration.")
- next
- end
-
- case type
- when "i"
- # if yum-dump was called with --installed this may not be true, but it's okay
- # since we don't touch the @available Set in reload_installed
- available = false
- installed = true
- when "a"
- available = true
- installed = false
- when "r"
- available = true
- installed = true
- end
-
- pkg = RPMDbPackage.new(name, epoch, version, release, arch, provides, installed, available, repoid)
- @rpmdb << pkg
- end
-
- error = status.stderr
- rescue Mixlib::ShellOut::CommandTimeout => e
- Chef::Log.error("#{helper} exceeded timeout #{Chef::Config[:yum_timeout]}")
- raise(e)
- end
-
- if status.exitstatus != 0
- raise Chef::Exceptions::Package, "Yum failed - #{status.inspect} - returns: #{error}"
- else
- unless one_line
- Chef::Log.warn("Odd, no output from yum-dump.py. Please check " +
- "your yum configuration.")
- end
- end
-
- # A reload method must be called before the cache is altered
- @next_refresh = :none
- end
-
- def python_bin
- yum_executable = which(yum_binary)
- if yum_executable && shabang?(yum_executable)
- shabang_or_fallback(extract_interpreter(yum_executable))
- else
- Chef::Log.warn("Yum executable not found or doesn't start with #!. Using default python.")
- "/usr/bin/python"
- end
- rescue StandardError => e
- Chef::Log.warn("An error occurred attempting to determine correct python executable. Using default.")
- Chef::Log.debug(e)
- "/usr/bin/python"
- end
-
- def extract_interpreter(file)
- ::File.open(file, "r", &:readline)[2..-1].strip
- end
-
- # dnf based systems have a yum shim that has /bin/bash as the interpreter. Don't use this.
- def shabang_or_fallback(interpreter)
- if interpreter == "/bin/bash"
- Chef::Log.warn("Yum executable interpreter is /bin/bash. Falling back to default python.")
- "/usr/bin/python"
- else
- interpreter
- end
- end
-
- def shabang?(file)
- ::File.open(file, "r") do |f|
- f.read(2) == '#!'
- end
- rescue Errno::ENOENT
- false
- end
-
- def reload
- @next_refresh = :all
- end
-
- def reload_installed
- @next_refresh = :installed
- end
-
- def reload_provides
- @next_refresh = :provides
- end
-
- def reset
- @rpmdb.clear
- end
-
- def reset_installed
- @rpmdb.clear_installed
- end
-
- # Querying the cache
- #
-
- # Check for package by name or name+arch
- def package_available?(package_name)
- refresh
-
- if @rpmdb.lookup(package_name)
- return true
- else
- if package_name =~ %r{^(.*)\.(.*)$}
- pkg_name = $1
- pkg_arch = $2
-
- if matches = @rpmdb.lookup(pkg_name)
- matches.each do |m|
- return true if m.arch == pkg_arch
- end
- end
- end
- end
-
- return false
- end
-
- # Returns a array of packages satisfying an RPMDependency
- def packages_from_require(rpmdep)
- refresh
- @rpmdb.whatprovides(rpmdep)
- end
-
- # Check if a package-version.arch is available to install
- def version_available?(package_name, desired_version, arch = nil)
- version(package_name, arch, true, false) do |v|
- return true if desired_version == v
- end
-
- return false
- end
-
- # Return the source repository for a package-version.arch
- def package_repository(package_name, desired_version, arch = nil)
- package(package_name, arch, true, false) do |pkg|
- return pkg.repoid if desired_version == pkg.version.to_s
- end
-
- return nil
- end
-
- # Return the latest available version for a package.arch
- def available_version(package_name, arch = nil)
- version(package_name, arch, true, false)
- end
- alias :candidate_version :available_version
-
- # Return the currently installed version for a package.arch
- def installed_version(package_name, arch = nil)
- version(package_name, arch, false, true)
- end
-
- # Return an array of packages allowed to be installed multiple times, such as the kernel
- def allow_multi_install
- refresh
- @allow_multi_install
- end
-
- def enable_extra_repo_control(arg)
- # Don't touch cache if it's the same repos as the last load
- unless @extra_repo_control == arg
- @extra_repo_control = arg
- reload
- end
- end
-
- def disable_extra_repo_control
- # Only force reload when set
- if @extra_repo_control
- @extra_repo_control = nil
- reload
- end
- end
-
- private
-
- def version(package_name, arch = nil, is_available = false, is_installed = false)
- package(package_name, arch, is_available, is_installed) do |pkg|
- if block_given?
- yield pkg.version.to_s
- else
- # first match is latest version
- return pkg.version.to_s
- end
- end
-
- if block_given?
- return self
- else
- return nil
- end
- end
-
- def package(package_name, arch = nil, is_available = false, is_installed = false)
- refresh
- packages = @rpmdb[package_name]
- if packages
- packages.each do |pkg|
- if is_available
- next unless @rpmdb.available?(pkg)
- end
- if is_installed
- next unless @rpmdb.installed?(pkg)
- end
- if arch
- next unless pkg.arch == arch
- end
-
- if block_given?
- yield pkg
- else
- # first match is latest version
- return pkg
- end
- end
- end
-
- if block_given?
- return self
- else
- return nil
- end
- end
-
- # Parse provides from yum-dump.py output
- def parse_provides(string)
- ret = []
- # ['atk = 1.12.2-1.fc6', 'libatk-1.0.so.0']
- string.split(", ").each do |seg|
- # 'atk = 1.12.2-1.fc6'
- if seg =~ %r{^'(.*)'$}
- ret << RPMProvide.parse($1)
- end
- end
-
- return ret
- end
-
- end # YumCache
-
include Chef::Mixin::GetSourceFromPackage
def initialize(new_resource, run_context)
@@ -1126,7 +171,7 @@ class Chef
end
end
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource = Chef::Resource::YumPackage.new(@new_resource.name)
@current_resource.package_name(@new_resource.package_name)
installed_version = []
diff --git a/lib/chef/provider/package/yum/rpm_utils.rb b/lib/chef/provider/package/yum/rpm_utils.rb
new file mode 100644
index 0000000000..a748c664a9
--- /dev/null
+++ b/lib/chef/provider/package/yum/rpm_utils.rb
@@ -0,0 +1,642 @@
+
+# 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/provider/package"
+
+class Chef
+ class Provider
+ class Package
+ class Yum < Chef::Provider::Package
+ class RPMUtils
+ class << self
+
+ # RPM::Version version_parse equivalent
+ def version_parse(evr)
+ return if evr.nil?
+
+ epoch = nil
+ # assume this is a version
+ version = evr
+ release = nil
+
+ lead = 0
+ tail = evr.size
+
+ if %r{^([\d]+):}.match(evr) # rubocop:disable Performance/RedundantMatch
+ epoch = $1.to_i
+ lead = $1.length + 1
+ elsif evr[0].ord == ":".ord
+ epoch = 0
+ lead = 1
+ end
+
+ if %r{:?.*-(.*)$}.match(evr) # rubocop:disable Performance/RedundantMatch
+ release = $1
+ tail = evr.length - release.length - lead - 1
+
+ if release.empty?
+ release = nil
+ end
+ end
+
+ version = evr[lead, tail]
+ if version.empty?
+ version = nil
+ end
+
+ [ epoch, version, release ]
+ end
+
+ # verify
+ def isalnum(x)
+ isalpha(x) || isdigit(x)
+ end
+
+ def isalpha(x)
+ v = x.ord
+ (v >= 65 && v <= 90) || (v >= 97 && v <= 122)
+ end
+
+ def isdigit(x)
+ v = x.ord
+ v >= 48 && v <= 57
+ end
+
+ # based on the reference spec in lib/rpmvercmp.c in rpm 4.9.0
+ def rpmvercmp(x, y)
+ # easy! :)
+ return 0 if x == y
+
+ if x.nil?
+ x = ""
+ end
+
+ if y.nil?
+ y = ""
+ end
+
+ # not so easy :(
+ #
+ # takes 2 strings like
+ #
+ # x = "1.20.b18.el5"
+ # y = "1.20.b17.el5"
+ #
+ # breaks into purely alpha and numeric segments and compares them using
+ # some rules
+ #
+ # * 10 > 1
+ # * 1 > a
+ # * z > a
+ # * Z > A
+ # * z > Z
+ # * leading zeros are ignored
+ # * separators (periods, commas) are ignored
+ # * "1.20.b18.el5.extrastuff" > "1.20.b18.el5"
+
+ x_pos = 0 # overall string element reference position
+ x_pos_max = x.length - 1 # number of elements in string, starting from 0
+ x_seg_pos = 0 # segment string element reference position
+ x_comp = nil # segment to compare
+
+ y_pos = 0
+ y_seg_pos = 0
+ y_pos_max = y.length - 1
+ y_comp = nil
+
+ while x_pos <= x_pos_max && y_pos <= y_pos_max
+ # first we skip over anything non alphanumeric
+ while (x_pos <= x_pos_max) && (isalnum(x[x_pos]) == false)
+ x_pos += 1 # +1 over pos_max if end of string
+ end
+ while (y_pos <= y_pos_max) && (isalnum(y[y_pos]) == false)
+ y_pos += 1
+ end
+
+ # if we hit the end of either we are done matching segments
+ if (x_pos == x_pos_max + 1) || (y_pos == y_pos_max + 1)
+ break
+ end
+
+ # we are now at the start of a alpha or numeric segment
+ x_seg_pos = x_pos
+ y_seg_pos = y_pos
+
+ # grab segment so we can compare them
+ if isdigit(x[x_seg_pos].ord)
+ x_seg_is_num = true
+
+ # already know it's a digit
+ x_seg_pos += 1
+
+ # gather up our digits
+ while (x_seg_pos <= x_pos_max) && isdigit(x[x_seg_pos])
+ x_seg_pos += 1
+ end
+ # copy the segment but not the unmatched character that x_seg_pos will
+ # refer to
+ x_comp = x[x_pos, x_seg_pos - x_pos]
+
+ while (y_seg_pos <= y_pos_max) && isdigit(y[y_seg_pos])
+ y_seg_pos += 1
+ end
+ y_comp = y[y_pos, y_seg_pos - y_pos]
+ else
+ # we are comparing strings
+ x_seg_is_num = false
+
+ while (x_seg_pos <= x_pos_max) && isalpha(x[x_seg_pos])
+ x_seg_pos += 1
+ end
+ x_comp = x[x_pos, x_seg_pos - x_pos]
+
+ while (y_seg_pos <= y_pos_max) && isalpha(y[y_seg_pos])
+ y_seg_pos += 1
+ end
+ y_comp = y[y_pos, y_seg_pos - y_pos]
+ end
+
+ # if y_seg_pos didn't advance in the above loop it means the segments are
+ # different types
+ if y_pos == y_seg_pos
+ # numbers always win over letters
+ return x_seg_is_num ? 1 : -1
+ end
+
+ # move the ball forward before we mess with the segments
+ x_pos += x_comp.length # +1 over pos_max if end of string
+ y_pos += y_comp.length
+
+ # we are comparing numbers - simply convert them
+ if x_seg_is_num
+ x_comp = x_comp.to_i
+ y_comp = y_comp.to_i
+ end
+
+ # compares ints or strings
+ # don't return if equal - try the next segment
+ if x_comp > y_comp
+ return 1
+ elsif x_comp < y_comp
+ return -1
+ end
+
+ # if we've reached here than the segments are the same - try again
+ end
+
+ # we must have reached the end of one or both of the strings and they
+ # matched up until this point
+
+ # segments matched completely but the segment separators were different -
+ # rpm reference code treats these as equal.
+ if (x_pos == x_pos_max + 1) && (y_pos == y_pos_max + 1)
+ return 0
+ end
+
+ # the most unprocessed characters left wins
+ if (x_pos_max - x_pos) > (y_pos_max - y_pos)
+ return 1
+ else
+ return -1
+ end
+ end
+
+ end # self
+ end # RPMUtils
+
+ class RPMVersion
+ include Comparable
+
+ def initialize(*args)
+ if args.size == 1
+ @e, @v, @r = RPMUtils.version_parse(args[0])
+ elsif args.size == 3
+ @e = args[0].to_i
+ @v = args[1]
+ @r = args[2]
+ else
+ raise ArgumentError, "Expecting either 'epoch-version-release' or 'epoch, " +
+ "version, release'"
+ end
+ end
+ attr_reader :e, :v, :r
+ alias :epoch :e
+ alias :version :v
+ alias :release :r
+
+ def self.parse(*args)
+ self.new(*args)
+ end
+
+ def <=>(y)
+ compare_versions(y)
+ end
+
+ def compare(y)
+ compare_versions(y, false)
+ end
+
+ def partial_compare(y)
+ compare_versions(y, true)
+ end
+
+ # RPM::Version rpm_version_to_s equivalent
+ def to_s
+ if @r.nil?
+ @v
+ else
+ "#{@v}-#{@r}"
+ end
+ end
+
+ def evr
+ "#{@e}:#{@v}-#{@r}"
+ end
+
+ private
+
+ # Rough RPM::Version rpm_version_cmp equivalent - except much slower :)
+ #
+ # partial lets epoch and version segment equality be good enough to return equal, eg:
+ #
+ # 2:1.2-1 == 2:1.2
+ # 2:1.2-1 == 2:
+ #
+ def compare_versions(y, partial = false)
+ x = self
+
+ # compare epoch
+ if (x.e.nil? == false && x.e > 0) && y.e.nil?
+ return 1
+ elsif x.e.nil? && (y.e.nil? == false && y.e > 0)
+ return -1
+ elsif x.e.nil? == false && y.e.nil? == false
+ if x.e < y.e
+ return -1
+ elsif x.e > y.e
+ return 1
+ end
+ end
+
+ # compare version
+ if partial && (x.v.nil? || y.v.nil?)
+ return 0
+ elsif x.v.nil? == false && y.v.nil?
+ return 1
+ elsif x.v.nil? && y.v.nil? == false
+ return -1
+ elsif x.v.nil? == false && y.v.nil? == false
+ cmp = RPMUtils.rpmvercmp(x.v, y.v)
+ return cmp if cmp != 0
+ end
+
+ # compare release
+ if partial && (x.r.nil? || y.r.nil?)
+ return 0
+ elsif x.r.nil? == false && y.r.nil?
+ return 1
+ elsif x.r.nil? && y.r.nil? == false
+ return -1
+ elsif x.r.nil? == false && y.r.nil? == false
+ cmp = RPMUtils.rpmvercmp(x.r, y.r)
+ return cmp
+ end
+
+ return 0
+ end
+ end
+
+ class RPMPackage
+ include Comparable
+
+ def initialize(*args)
+ if args.size == 4
+ @n = args[0]
+ @version = RPMVersion.new(args[1])
+ @a = args[2]
+ @provides = args[3]
+ elsif args.size == 6
+ @n = args[0]
+ e = args[1].to_i
+ v = args[2]
+ r = args[3]
+ @version = RPMVersion.new(e, v, r)
+ @a = args[4]
+ @provides = args[5]
+ else
+ raise ArgumentError, "Expecting either 'name, epoch-version-release, arch, provides' " +
+ "or 'name, epoch, version, release, arch, provides'"
+ end
+
+ # We always have one, ourselves!
+ if @provides.empty?
+ @provides = [ RPMProvide.new(@n, @version.evr, :==) ]
+ end
+ end
+ attr_reader :n, :a, :version, :provides
+ alias :name :n
+ alias :arch :a
+
+ def <=>(y)
+ compare(y)
+ end
+
+ def compare(y)
+ x = self
+
+ # easy! :)
+ return 0 if x.nevra == y.nevra
+
+ # compare name
+ if x.n.nil? == false && y.n.nil?
+ return 1
+ elsif x.n.nil? && y.n.nil? == false
+ return -1
+ elsif x.n.nil? == false && y.n.nil? == false
+ if x.n < y.n
+ return -1
+ elsif x.n > y.n
+ return 1
+ end
+ end
+
+ # compare version
+ if x.version > y.version
+ return 1
+ elsif x.version < y.version
+ return -1
+ end
+
+ # compare arch
+ if x.a.nil? == false && y.a.nil?
+ return 1
+ elsif x.a.nil? && y.a.nil? == false
+ return -1
+ elsif x.a.nil? == false && y.a.nil? == false
+ if x.a < y.a
+ return -1
+ elsif x.a > y.a
+ return 1
+ end
+ end
+
+ return 0
+ end
+
+ def to_s
+ nevra
+ end
+
+ def nevra
+ "#{@n}-#{@version.evr}.#{@a}"
+ end
+ end
+
+ # Simple implementation from rpm and ruby-rpm reference code
+ class RPMDependency
+ def initialize(*args)
+ if args.size == 3
+ @name = args[0]
+ @version = RPMVersion.new(args[1])
+ # Our requirement to other dependencies
+ @flag = args[2] || :==
+ elsif args.size == 5
+ @name = args[0]
+ e = args[1].to_i
+ v = args[2]
+ r = args[3]
+ @version = RPMVersion.new(e, v, r)
+ @flag = args[4] || :==
+ else
+ raise ArgumentError, "Expecting either 'name, epoch-version-release, flag' or " +
+ "'name, epoch, version, release, flag'"
+ end
+ end
+ attr_reader :name, :version, :flag
+
+ # Parses 2 forms:
+ #
+ # "mtr >= 2:0.71-3.0"
+ # "mta"
+ def self.parse(string)
+ if %r{^(\S+)\s+(>|>=|=|==|<=|<)\s+(\S+)$}.match(string) # rubocop:disable Performance/RedundantMatch
+ name = $1
+ if $2 == "="
+ flag = :==
+ else
+ flag = :"#{$2}"
+ end
+ version = $3
+
+ return self.new(name, version, flag)
+ else
+ name = string
+ return self.new(name, nil, nil)
+ end
+ end
+
+ # Test if another RPMDependency satisfies our requirements
+ def satisfy?(y)
+ unless y.kind_of?(RPMDependency)
+ raise ArgumentError, "Expecting an RPMDependency object"
+ end
+
+ x = self
+
+ # Easy!
+ if x.name != y.name
+ return false
+ end
+
+ # Partial compare
+ #
+ # eg: x.version 2.3 == y.version 2.3-1
+ sense = x.version.partial_compare(y.version)
+
+ # Thanks to rpmdsCompare() rpmds.c
+ if (sense < 0) && ((x.flag == :> || x.flag == :>=) || (y.flag == :<= || y.flag == :<))
+ return true
+ elsif (sense > 0) && ((x.flag == :< || x.flag == :<=) || (y.flag == :>= || y.flag == :>))
+ return true
+ elsif sense == 0 && (
+ ((x.flag == :== || x.flag == :<= || x.flag == :>=) && (y.flag == :== || y.flag == :<= || y.flag == :>=)) ||
+ (x.flag == :< && y.flag == :<) ||
+ (x.flag == :> && y.flag == :>)
+ )
+ return true
+ end
+
+ return false
+ end
+ end
+
+ class RPMProvide < RPMDependency; end
+ class RPMRequire < RPMDependency; end
+
+ class RPMDbPackage < RPMPackage
+ # <rpm parts>, installed, available
+ def initialize(*args)
+ @repoid = args.pop
+ # state
+ @available = args.pop
+ @installed = args.pop
+ super(*args)
+ end
+ attr_reader :repoid, :available, :installed
+ end
+
+ # Simple storage for RPMPackage objects - keeps them unique and sorted
+ class RPMDb
+ def initialize
+ # package name => [ RPMPackage, RPMPackage ] of different versions
+ @rpms = Hash.new
+ # package nevra => RPMPackage for lookups
+ @index = Hash.new
+ # provide name (aka feature) => [RPMPackage, RPMPackage] each providing this feature
+ @provides = Hash.new
+ # RPMPackages listed as available
+ @available = Set.new
+ # RPMPackages listed as installed
+ @installed = Set.new
+ end
+
+ def [](package_name)
+ self.lookup(package_name)
+ end
+
+ # Lookup package_name and return a descending array of package objects
+ def lookup(package_name)
+ pkgs = @rpms[package_name]
+ if pkgs
+ return pkgs.sort.reverse
+ else
+ return nil
+ end
+ end
+
+ def lookup_provides(provide_name)
+ @provides[provide_name]
+ end
+
+ # Using the package name as a key, and nevra for an index, keep a unique list of packages.
+ # The available/installed state can be overwritten for existing packages.
+ def push(*args)
+ args.flatten.each do |new_rpm|
+ unless new_rpm.kind_of?(RPMDbPackage)
+ raise ArgumentError, "Expecting an RPMDbPackage object"
+ end
+
+ @rpms[new_rpm.n] ||= Array.new
+
+ # we may already have this one, like when the installed list is refreshed
+ idx = @index[new_rpm.nevra]
+ if idx
+ # grab the existing package if it's not
+ curr_rpm = idx
+ else
+ @rpms[new_rpm.n] << new_rpm
+
+ new_rpm.provides.each do |provide|
+ @provides[provide.name] ||= Array.new
+ @provides[provide.name] << new_rpm
+ end
+
+ curr_rpm = new_rpm
+ end
+
+ # Track the nevra -> RPMPackage association to avoid having to compare versions
+ # with @rpms[new_rpm.n] on the next round
+ @index[new_rpm.nevra] = curr_rpm
+
+ # these are overwritten for existing packages
+ if new_rpm.available
+ @available << curr_rpm
+ end
+ if new_rpm.installed
+ @installed << curr_rpm
+ end
+ end
+ end
+
+ def <<(*args)
+ self.push(args)
+ end
+
+ def clear
+ @rpms.clear
+ @index.clear
+ @provides.clear
+ clear_available
+ clear_installed
+ end
+
+ def clear_available
+ @available.clear
+ end
+
+ def clear_installed
+ @installed.clear
+ end
+
+ def size
+ @rpms.size
+ end
+ alias :length :size
+
+ def available_size
+ @available.size
+ end
+
+ def installed_size
+ @installed.size
+ end
+
+ def available?(package)
+ @available.include?(package)
+ end
+
+ def installed?(package)
+ @installed.include?(package)
+ end
+
+ def whatprovides(rpmdep)
+ unless rpmdep.kind_of?(RPMDependency)
+ raise ArgumentError, "Expecting an RPMDependency object"
+ end
+
+ what = []
+
+ packages = lookup_provides(rpmdep.name)
+ if packages
+ packages.each do |pkg|
+ pkg.provides.each do |provide|
+ if provide.satisfy?(rpmdep)
+ what << pkg
+ end
+ end
+ end
+ end
+
+ return what
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/yum-dump.py b/lib/chef/provider/package/yum/yum-dump.py
index 6183460195..6183460195 100644
--- a/lib/chef/provider/package/yum-dump.py
+++ b/lib/chef/provider/package/yum/yum-dump.py
diff --git a/lib/chef/provider/package/yum/yum_cache.rb b/lib/chef/provider/package/yum/yum_cache.rb
new file mode 100644
index 0000000000..fb25a91c8c
--- /dev/null
+++ b/lib/chef/provider/package/yum/yum_cache.rb
@@ -0,0 +1,376 @@
+
+# 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/config"
+require "chef/provider/package"
+require "chef/mixin/which"
+require "chef/mixin/shell_out"
+require "singleton"
+require "chef/provider/package/yum/rpm_utils"
+
+class Chef
+ class Provider
+ class Package
+ class Yum < Chef::Provider::Package
+ # Cache for our installed and available packages, pulled in from yum-dump.py
+ class YumCache
+ include Chef::Mixin::Which
+ include Chef::Mixin::ShellOut
+ include Singleton
+
+ attr_accessor :yum_binary
+
+ def initialize
+ @rpmdb = RPMDb.new
+
+ # Next time @rpmdb is accessed:
+ # :all - Trigger a run of "yum-dump.py --options --installed-provides", updates
+ # yum's cache and parses options from /etc/yum.conf. Pulls in Provides
+ # dependency data for installed packages only - this data is slow to
+ # gather.
+ # :provides - Same as :all but pulls in Provides data for available packages as well.
+ # Used as a last resort when we can't find a Provides match.
+ # :installed - Trigger a run of "yum-dump.py --installed", only reads the local rpm
+ # db. Used between client runs for a quick refresh.
+ # :none - Do nothing, a call to one of the reload methods is required.
+ @next_refresh = :all
+
+ @allow_multi_install = []
+
+ @extra_repo_control = nil
+
+ # these are for subsequent runs if we are on an interval
+ Chef::Client.when_run_starts do
+ YumCache.instance.reload
+ end
+ end
+
+ attr_reader :extra_repo_control
+
+ # Cache management
+ #
+
+ def yum_dump_path
+ ::File.join(::File.dirname(__FILE__), "yum-dump.py")
+ end
+
+ def refresh
+ case @next_refresh
+ when :none
+ return nil
+ when :installed
+ reset_installed
+ # fast
+ opts = " --installed"
+ when :all
+ reset
+ # medium
+ opts = " --options --installed-provides"
+ when :provides
+ reset
+ # slow!
+ opts = " --options --all-provides"
+ else
+ raise ArgumentError, "Unexpected value in next_refresh: #{@next_refresh}"
+ end
+
+ if @extra_repo_control
+ opts << " #{@extra_repo_control}"
+ end
+
+ opts << " --yum-lock-timeout #{Chef::Config[:yum_lock_timeout]}"
+
+ one_line = false
+ error = nil
+
+ status = nil
+
+ begin
+ status = shell_out!("#{python_bin} #{yum_dump_path}#{opts}", :timeout => Chef::Config[:yum_timeout])
+ status.stdout.each_line do |line|
+ one_line = true
+
+ line.chomp!
+ if line =~ %r{\[option (.*)\] (.*)}
+ if $1 == "installonlypkgs"
+ @allow_multi_install = $2.split
+ else
+ raise Chef::Exceptions::Package, "Strange, unknown option line '#{line}' from yum-dump.py"
+ end
+ next
+ end
+
+ if line =~ %r{^(\S+) ([0-9]+) (\S+) (\S+) (\S+) \[(.*)\] ([i,a,r]) (\S+)$}
+ name = $1
+ epoch = $2
+ version = $3
+ release = $4
+ arch = $5
+ provides = parse_provides($6)
+ type = $7
+ repoid = $8
+ else
+ Chef::Log.warn("Problem parsing line '#{line}' from yum-dump.py! " +
+ "Please check your yum configuration.")
+ next
+ end
+
+ case type
+ when "i"
+ # if yum-dump was called with --installed this may not be true, but it's okay
+ # since we don't touch the @available Set in reload_installed
+ available = false
+ installed = true
+ when "a"
+ available = true
+ installed = false
+ when "r"
+ available = true
+ installed = true
+ end
+
+ pkg = RPMDbPackage.new(name, epoch, version, release, arch, provides, installed, available, repoid)
+ @rpmdb << pkg
+ end
+
+ error = status.stderr
+ rescue Mixlib::ShellOut::CommandTimeout => e
+ Chef::Log.error("#{yum_dump_path} exceeded timeout #{Chef::Config[:yum_timeout]}")
+ raise(e)
+ end
+
+ if status.exitstatus != 0
+ raise Chef::Exceptions::Package, "Yum failed - #{status.inspect} - returns: #{error}"
+ else
+ unless one_line
+ Chef::Log.warn("Odd, no output from yum-dump.py. Please check " +
+ "your yum configuration.")
+ end
+ end
+
+ # A reload method must be called before the cache is altered
+ @next_refresh = :none
+ end
+
+ def python_bin
+ yum_executable = which(yum_binary)
+ if yum_executable && shabang?(yum_executable)
+ shabang_or_fallback(extract_interpreter(yum_executable))
+ else
+ Chef::Log.warn("Yum executable not found or doesn't start with #!. Using default python.")
+ "/usr/bin/python"
+ end
+ rescue StandardError => e
+ Chef::Log.warn("An error occurred attempting to determine correct python executable. Using default.")
+ Chef::Log.debug(e)
+ "/usr/bin/python"
+ end
+
+ def extract_interpreter(file)
+ ::File.open(file, "r", &:readline)[2..-1].strip
+ end
+
+ # dnf based systems have a yum shim that has /bin/bash as the interpreter. Don't use this.
+ def shabang_or_fallback(interpreter)
+ if interpreter == "/bin/bash"
+ Chef::Log.warn("Yum executable interpreter is /bin/bash. Falling back to default python.")
+ "/usr/bin/python"
+ else
+ interpreter
+ end
+ end
+
+ def shabang?(file)
+ ::File.open(file, "r") do |f|
+ f.read(2) == '#!'
+ end
+ rescue Errno::ENOENT
+ false
+ end
+
+ def reload
+ @next_refresh = :all
+ end
+
+ def reload_installed
+ @next_refresh = :installed
+ end
+
+ def reload_provides
+ @next_refresh = :provides
+ end
+
+ def reset
+ @rpmdb.clear
+ end
+
+ def reset_installed
+ @rpmdb.clear_installed
+ end
+
+ # Querying the cache
+ #
+
+ # Check for package by name or name+arch
+ def package_available?(package_name)
+ refresh
+
+ if @rpmdb.lookup(package_name)
+ return true
+ else
+ if package_name =~ %r{^(.*)\.(.*)$}
+ pkg_name = $1
+ pkg_arch = $2
+
+ if matches = @rpmdb.lookup(pkg_name)
+ matches.each do |m|
+ return true if m.arch == pkg_arch
+ end
+ end
+ end
+ end
+
+ return false
+ end
+
+ # Returns a array of packages satisfying an RPMDependency
+ def packages_from_require(rpmdep)
+ refresh
+ @rpmdb.whatprovides(rpmdep)
+ end
+
+ # Check if a package-version.arch is available to install
+ def version_available?(package_name, desired_version, arch = nil)
+ version(package_name, arch, true, false) do |v|
+ return true if desired_version == v
+ end
+
+ return false
+ end
+
+ # Return the source repository for a package-version.arch
+ def package_repository(package_name, desired_version, arch = nil)
+ package(package_name, arch, true, false) do |pkg|
+ return pkg.repoid if desired_version == pkg.version.to_s
+ end
+
+ return nil
+ end
+
+ # Return the latest available version for a package.arch
+ def available_version(package_name, arch = nil)
+ version(package_name, arch, true, false)
+ end
+ alias :candidate_version :available_version
+
+ # Return the currently installed version for a package.arch
+ def installed_version(package_name, arch = nil)
+ version(package_name, arch, false, true)
+ end
+
+ # Return an array of packages allowed to be installed multiple times, such as the kernel
+ def allow_multi_install
+ refresh
+ @allow_multi_install
+ end
+
+ def enable_extra_repo_control(arg)
+ # Don't touch cache if it's the same repos as the last load
+ unless @extra_repo_control == arg
+ @extra_repo_control = arg
+ reload
+ end
+ end
+
+ def disable_extra_repo_control
+ # Only force reload when set
+ if @extra_repo_control
+ @extra_repo_control = nil
+ reload
+ end
+ end
+
+ private
+
+ def version(package_name, arch = nil, is_available = false, is_installed = false)
+ package(package_name, arch, is_available, is_installed) do |pkg|
+ if block_given?
+ yield pkg.version.to_s
+ else
+ # first match is latest version
+ return pkg.version.to_s
+ end
+ end
+
+ if block_given?
+ return self
+ else
+ return nil
+ end
+ end
+
+ def package(package_name, arch = nil, is_available = false, is_installed = false)
+ refresh
+ packages = @rpmdb[package_name]
+ if packages
+ packages.each do |pkg|
+ if is_available
+ next unless @rpmdb.available?(pkg)
+ end
+ if is_installed
+ next unless @rpmdb.installed?(pkg)
+ end
+ if arch
+ next unless pkg.arch == arch
+ end
+
+ if block_given?
+ yield pkg
+ else
+ # first match is latest version
+ return pkg
+ end
+ end
+ end
+
+ if block_given?
+ return self
+ else
+ return nil
+ end
+ end
+
+ # Parse provides from yum-dump.py output
+ def parse_provides(string)
+ ret = []
+ # ['atk = 1.12.2-1.fc6', 'libatk-1.0.so.0']
+ string.split(", ").each do |seg|
+ # 'atk = 1.12.2-1.fc6'
+ if seg =~ %r{^'(.*)'$}
+ ret << RPMProvide.parse($1)
+ end
+ end
+
+ return ret
+ end
+
+ end # YumCache
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/systemd_unit.rb b/lib/chef/provider/systemd_unit.rb
index a73d0bc0ad..b96a336765 100644
--- a/lib/chef/provider/systemd_unit.rb
+++ b/lib/chef/provider/systemd_unit.rb
@@ -143,6 +143,24 @@ class Chef
end
end
+ def action_try_restart
+ converge_by("try-restarting unit: #{new_resource.name}") do
+ systemctl_execute!("try-restart", new_resource.name)
+ end
+ end
+
+ def action_reload_or_restart
+ converge_by("reload-or-restarting unit: #{new_resource.name}") do
+ systemctl_execute!("reload-or-restart", new_resource.name)
+ end
+ end
+
+ def action_reload_or_try_restart
+ converge_by("reload-or-try-restarting unit: #{new_resource.name}") do
+ systemctl_execute!("reload-or-try-restart", new_resource.name)
+ end
+ end
+
def active?
systemctl_execute("is-active", new_resource.name).exitstatus == 0
end
diff --git a/lib/chef/resource/file.rb b/lib/chef/resource/file.rb
index ac6dc5fbdb..7088e7613e 100644
--- a/lib/chef/resource/file.rb
+++ b/lib/chef/resource/file.rb
@@ -21,6 +21,7 @@ require "chef/resource"
require "chef/platform/query_helpers"
require "chef/mixin/securable"
require "chef/resource/file/verification"
+require "pathname"
class Chef
class Resource
@@ -49,7 +50,7 @@ class Chef
allowed_actions :create, :delete, :touch, :create_if_missing
property :path, String, name_property: true, identity: true
- property :atomic_update, [ true, false ], desired_state: false, default: lazy { Chef::Config[:file_atomic_update] }
+ property :atomic_update, [ true, false ], desired_state: false, default: lazy { |r| r.docker? && r.special_docker_files?(r.path) ? false : Chef::Config[:file_atomic_update] }
property :backup, [ Integer, false ], desired_state: false, default: 5
property :checksum, [ /^[a-zA-Z0-9]{64}$/, nil ]
property :content, [ String, nil ], desired_state: false
@@ -78,6 +79,10 @@ class Chef
end
state_attrs
end
+
+ def special_docker_files?(file)
+ %w{/etc/hosts /etc/hostname /etc/resolv.conf}.include?(Pathname(file).cleanpath.to_path)
+ end
end
end
end
diff --git a/lib/chef/resource/systemd_unit.rb b/lib/chef/resource/systemd_unit.rb
index 525e9ab35e..688f2e9dcd 100644
--- a/lib/chef/resource/systemd_unit.rb
+++ b/lib/chef/resource/systemd_unit.rb
@@ -29,7 +29,9 @@ class Chef
:enable, :disable,
:mask, :unmask,
:start, :stop,
- :restart, :reload
+ :restart, :reload,
+ :try_restart, :reload_or_restart,
+ :reload_or_try_restart
property :enabled, [TrueClass, FalseClass]
property :active, [TrueClass, FalseClass]
diff --git a/lib/chef/version.rb b/lib/chef/version.rb
index 15cadb6eae..bee49dae6c 100644
--- a/lib/chef/version.rb
+++ b/lib/chef/version.rb
@@ -21,7 +21,7 @@
class Chef
CHEF_ROOT = File.expand_path("../..", __FILE__)
- VERSION = "12.11.3"
+ VERSION = "12.11.27"
end
#
diff --git a/omnibus/Gemfile.lock b/omnibus/Gemfile.lock
index 0ee716cbb3..f165fa5d12 100644
--- a/omnibus/Gemfile.lock
+++ b/omnibus/Gemfile.lock
@@ -1,13 +1,13 @@
GIT
remote: https://github.com/chef/omnibus-software.git
- revision: 91797cdae99ca91cb87af877ceb4953610d45152
+ revision: 2f04eff7dbec575cb2985d846dacd02a422cd36f
specs:
omnibus-software (4.0.0)
omnibus (>= 5.2.0)
GIT
remote: https://github.com/chef/omnibus.git
- revision: 7c98e2bbceb741711de408dceba7bd7c813713e4
+ revision: a36e70caedceadfcf0d85e2adef44ba0218a60a6
specs:
omnibus (5.4.0)
aws-sdk (~> 2)
@@ -38,12 +38,12 @@ GEM
addressable (2.3.8)
artifactory (2.3.2)
awesome_print (1.6.1)
- aws-sdk (2.3.4)
- aws-sdk-resources (= 2.3.4)
- aws-sdk-core (2.3.4)
+ aws-sdk (2.3.9)
+ aws-sdk-resources (= 2.3.9)
+ aws-sdk-core (2.3.9)
jmespath (~> 1.0)
- aws-sdk-resources (2.3.4)
- aws-sdk-core (= 2.3.4)
+ aws-sdk-resources (2.3.9)
+ aws-sdk-core (= 2.3.9)
berkshelf (3.3.0)
addressable (~> 2.3.4)
berkshelf-api-client (~> 1.2)
@@ -75,7 +75,7 @@ GEM
buff-shell_out (0.2.0)
buff-ruby_engine (~> 0.1.0)
builder (3.2.2)
- byebug (9.0.3)
+ byebug (9.0.4)
celluloid (0.16.0)
timers (~> 4.0.0)
celluloid-io (0.16.2)
@@ -98,6 +98,7 @@ GEM
faraday (0.9.2)
multipart-post (>= 1.2, < 3)
ffi (1.9.10)
+ ffi (1.9.10-x86-mingw32)
ffi-yajl (2.2.3)
libyajl2 (~> 1.2)
fuzzyurl (0.8.0)
@@ -107,6 +108,7 @@ GEM
builder (>= 2.1.2)
hashie (3.4.4)
hitimes (1.2.4)
+ hitimes (1.2.4-x86-mingw32)
httpclient (2.6.0.1)
iostruct (0.0.4)
ipaddress (0.8.3)
@@ -130,14 +132,17 @@ GEM
rspec-mocks (~> 3.2)
mixlib-cli (1.6.0)
mixlib-config (2.2.1)
- mixlib-install (1.0.11)
+ mixlib-install (1.0.12)
artifactory
mixlib-shellout
mixlib-versioning
mixlib-log (1.6.0)
mixlib-shellout (2.2.6)
+ mixlib-shellout (2.2.6-universal-mingw32)
+ win32-process (~> 0.8.2)
+ wmi-lite (~> 1.0)
mixlib-versioning (1.1.0)
- multi_json (1.12.0)
+ multi_json (1.12.1)
multipart-post (1.2.0)
net-scp (1.2.1)
net-ssh (>= 2.6.5)
@@ -224,6 +229,8 @@ GEM
varia_model (0.4.1)
buff-extensions (~> 1.0)
hashie (>= 2.0.2, < 4.0.0)
+ win32-process (0.8.3)
+ ffi (>= 1.0.0)
winrm (1.8.1)
builder (>= 2.1.2)
gssapi (~> 1.2)
@@ -242,6 +249,7 @@ GEM
PLATFORMS
ruby
+ x86-mingw32
DEPENDENCIES
berkshelf (~> 3.0)
@@ -256,4 +264,4 @@ DEPENDENCIES
winrm-fs (~> 0.4.0)
BUNDLED WITH
- 1.11.2
+ 1.12.5
diff --git a/omnibus/config/software/chef-appbundle.rb b/omnibus/config/software/chef-appbundle.rb
index 19228738c3..495f58bfb1 100644
--- a/omnibus/config/software/chef-appbundle.rb
+++ b/omnibus/config/software/chef-appbundle.rb
@@ -1,5 +1,8 @@
name "chef-appbundle"
default_version "local_source"
+
+license :project_license
+
source path: project.files_path
dependency "chef"
diff --git a/omnibus_overrides.rb b/omnibus_overrides.rb
index 3414f217fa..d971dd99fa 100644
--- a/omnibus_overrides.rb
+++ b/omnibus_overrides.rb
@@ -1,6 +1,6 @@
# DO NOT EDIT. Generated by "rake dependencies". Edit version_policy.rb instead.
override :rubygems, version: "2.6.4"
-override :bundler, version: "1.12.3"
+override :bundler, version: "1.11.2"
override "libffi", version: "3.2.1"
override "libiconv", version: "1.14"
override "liblzma", version: "5.2.2"
diff --git a/spec/functional/resource/chocolatey_package_spec.rb b/spec/functional/resource/chocolatey_package_spec.rb
index fb51fd2d64..7bb6698daf 100644
--- a/spec/functional/resource/chocolatey_package_spec.rb
+++ b/spec/functional/resource/chocolatey_package_spec.rb
@@ -82,11 +82,6 @@ describe Chef::Resource::ChocolateyPackage, :windows_only do
subject.package_name "blah"
expect { subject.run_action(:install) }.to raise_error Chef::Exceptions::Package
end
-
- it "raises if package version is not found" do
- subject.version "3.0"
- expect { subject.run_action(:install) }.to raise_error Chef::Exceptions::Package
- end
end
context "upgrading a package" do
diff --git a/spec/functional/resource/dsc_script_spec.rb b/spec/functional/resource/dsc_script_spec.rb
index 42c23a695f..dc687ec074 100644
--- a/spec/functional/resource/dsc_script_spec.rb
+++ b/spec/functional/resource/dsc_script_spec.rb
@@ -469,6 +469,7 @@ EOF
end
it "allows the use of ps_credential" do
+ pending("Pended until we can adjust the test cert to meet the WMF 5 cert requirements.")
expect(user_exists?(dsc_user)).to eq(false)
powershell_script_resource.run_action(:run)
expect(File).to exist(configuration_data_path)
diff --git a/spec/integration/client/exit_code_spec.rb b/spec/integration/client/exit_code_spec.rb
new file mode 100644
index 0000000000..30020f6a3f
--- /dev/null
+++ b/spec/integration/client/exit_code_spec.rb
@@ -0,0 +1,245 @@
+
+require "support/shared/integration/integration_helper"
+require "chef/mixin/shell_out"
+require "tiny_server"
+require "tmpdir"
+require "chef/platform"
+
+describe "chef-client" do
+
+ include IntegrationSupport
+ include Chef::Mixin::ShellOut
+
+ let(:chef_dir) { File.join(File.dirname(__FILE__), "..", "..", "..", "bin") }
+
+ # Invoke `chef-client` as `ruby PATH/TO/chef-client`. This ensures the
+ # following constraints are satisfied:
+ # * Windows: windows can only run batch scripts as bare executables. Rubygems
+ # creates batch wrappers for installed gems, but we don't have batch wrappers
+ # in the source tree.
+ # * Other `chef-client` in PATH: A common case is running the tests on a
+ # machine that has omnibus chef installed. In that case we need to ensure
+ # we're running `chef-client` from the source tree and not the external one.
+ # cf. CHEF-4914
+ let(:chef_client) { "ruby '#{chef_dir}/chef-client' --no-fork --minimal-ohai" }
+
+ 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
+
+ def setup_client_rb
+ file "config/client.rb", <<EOM
+local_mode true
+cookbook_path "#{path_to('cookbooks')}"
+EOM
+ end
+
+ def setup_client_rb_with_audit_mode
+ file "config/client.rb", <<EOM
+local_mode true
+cookbook_path "#{path_to('cookbooks')}"
+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])
+ 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])
+ 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(4 - 4).to eq(1)
+ end
+end
+RECIPE
+ end
+
+ it "exits with AUDIT_MODE_FAILURE, 42" do
+ setup_client_rb_with_audit_mode
+ run_chef_client_and_expect_exit_code 42
+ 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 GENERIC_FAILURE exit code, 1" do
+ setup_client_rb
+ run_chef_client_and_expect_exit_code 1
+ 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 GENERIC_FAILURE exit code, 1" do
+ setup_client_rb
+ run_chef_client_and_expect_exit_code 1
+ end
+ end
+
+ context "when a reboot exception is raised (like from the reboot resource)" do
+ before do
+ file "cookbooks/x/recipes/default.rb", <<EOM
+raise Chef::Exceptions::Reboot.new
+EOM
+ end
+
+ it "exits with REBOOT_SCHEDULED, 35" do
+ setup_client_rb
+ run_chef_client_and_expect_exit_code 35
+ 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 REBOOT_FAILED, 41" do
+ setup_client_rb
+ run_chef_client_and_expect_exit_code 41
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/unit/application/apply_spec.rb b/spec/unit/application/apply_spec.rb
index 6473666fbf..0af3916134 100644
--- a/spec/unit/application/apply_spec.rb
+++ b/spec/unit/application/apply_spec.rb
@@ -52,7 +52,8 @@ describe Chef::Application::Apply do
describe "when recipe is nil" do
it "should raise a fatal with the missing filename message" do
- expect(Chef::Application).to receive(:fatal!).with("No recipe file was provided", 1)
+ expect(Chef::Application).to receive(:fatal!).with("No recipe file was provided",
+ Chef::Exceptions::RecipeNotFound.new)
@app.read_recipe_file(nil)
end
end
@@ -61,7 +62,8 @@ describe Chef::Application::Apply do
allow(File).to receive(:exist?).with(@recipe_path).and_return(false)
end
it "should raise a fatal with the file doesn't exist message" do
- expect(Chef::Application).to receive(:fatal!).with(/^No file exists at/, 1)
+ expect(Chef::Application).to receive(:fatal!).with(/^No file exists at/,
+ Chef::Exceptions::RecipeNotFound.new)
@app.read_recipe_file(@recipe_file_name)
end
end
diff --git a/spec/unit/application/exit_code_spec.rb b/spec/unit/application/exit_code_spec.rb
new file mode 100644
index 0000000000..73a113e554
--- /dev/null
+++ b/spec/unit/application/exit_code_spec.rb
@@ -0,0 +1,231 @@
+#
+# Author:: Steven Murawski (<smurawski@chef.io>)
+# Copyright:: Copyright 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"
+require "spec_helper"
+
+require "chef/application/exit_code"
+
+describe Chef::Application::ExitCode do
+
+ let(:exit_codes) { Chef::Application::ExitCode }
+
+ let(:valid_rfc_exit_codes) { Chef::Application::ExitCode::VALID_RFC_062_EXIT_CODES.values }
+
+ context "Validates the return codes from RFC 062" do
+
+ before do
+ allow(Chef::Config).to receive(:[]).with(:exit_status).and_return(:enabled)
+ end
+
+ it "validates a SUCCESS return code of 0" do
+ expect(valid_rfc_exit_codes.include?(0)).to eq(true)
+ end
+
+ it "validates a GENERIC_FAILURE return code of 1" do
+ expect(valid_rfc_exit_codes.include?(1)).to eq(true)
+ end
+
+ it "validates a SIGINT_RECEIVED return code of 2" do
+ expect(valid_rfc_exit_codes.include?(2)).to eq(true)
+ end
+
+ it "validates a SIGTERM_RECEIVED return code of 3" do
+ expect(valid_rfc_exit_codes.include?(3)).to eq(true)
+ end
+
+ it "validates a AUDIT_MODE_FAILURE return code of 42" do
+ expect(valid_rfc_exit_codes.include?(42)).to eq(true)
+ end
+
+ it "validates a REBOOT_SCHEDULED return code of 35" do
+ expect(valid_rfc_exit_codes.include?(35)).to eq(true)
+ end
+
+ it "validates a REBOOT_NEEDED return code of 37" do
+ expect(valid_rfc_exit_codes.include?(37)).to eq(true)
+ end
+
+ it "validates a REBOOT_FAILED return code of 41" do
+ expect(valid_rfc_exit_codes.include?(41)).to eq(true)
+ 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
+ warn = "Chef RFC 062 (https://github.com/chef/chef-rfc/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."
+ expect(Chef).to receive(:log_deprecation).with(warn)
+ 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
+ warn = "Chef RFC 062 (https://github.com/chef/chef-rfc/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."
+ expect(Chef).not_to receive(:log_deprecation).with(warn)
+ 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::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
+ warn = "Chef RFC 062 (https://github.com/chef/chef-rfc/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."
+ expect(Chef).to receive(:log_deprecation).with(warn)
+ expect(exit_codes.normalize_exit_code(151)).to eq(1)
+ end
+
+ it "returns a GENERIC_FAILURE for non-RFC exit codes" do
+ expect(exit_codes.normalize_exit_code(151)).to eq(1)
+ end
+
+ it "returns GENERIC_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 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
+
+ it "returns AUDIT_MODE_FAILURE when there is an audit error" do
+ audit_error = Chef::Exceptions::AuditError.new("BOOM")
+ runtime_error = Chef::Exceptions::RunFailedWrappingError.new(audit_error)
+ expect(exit_codes.normalize_exit_code(runtime_error)).to eq(42)
+ end
+
+ it "returns REBOOT_SCHEDULED when there is an reboot requested" do
+ reboot_error = Chef::Exceptions::Reboot.new("BOOM")
+ runtime_error = Chef::Exceptions::RunFailedWrappingError.new(reboot_error)
+ expect(exit_codes.normalize_exit_code(runtime_error)).to eq(35)
+ end
+
+ it "returns REBOOT_FAILED when the reboot command fails" do
+ reboot_error = Chef::Exceptions::RebootFailed.new("BOOM")
+ runtime_error = Chef::Exceptions::RunFailedWrappingError.new(reboot_error)
+ expect(exit_codes.normalize_exit_code(runtime_error)).to eq(41)
+ end
+
+ it "returns REBOOT_NEEDED when a reboot is pending" do
+ reboot_error = Chef::Exceptions::RebootPending.new("BOOM")
+ runtime_error = Chef::Exceptions::RunFailedWrappingError.new(reboot_error)
+ expect(exit_codes.normalize_exit_code(runtime_error)).to eq(37)
+ end
+
+ it "returns SIGINT_RECEIVED when a SIGINT is received." do
+ sigint_error = Chef::Exceptions::SigInt.new("BOOM")
+ runtime_error = Chef::Exceptions::RunFailedWrappingError.new(sigint_error)
+ expect(exit_codes.normalize_exit_code(runtime_error)).to eq(2)
+ end
+
+ it "returns SIGTERM_RECEIVED when a SIGTERM is received." do
+ sigterm_error = Chef::Exceptions::SigTerm.new("BOOM")
+ runtime_error = Chef::Exceptions::RunFailedWrappingError.new(sigterm_error)
+ expect(exit_codes.normalize_exit_code(runtime_error)).to eq(3)
+ end
+ end
+
+end
diff --git a/spec/unit/application/solo_spec.rb b/spec/unit/application/solo_spec.rb
index 1c8ec2e11c..bb29261f5a 100644
--- a/spec/unit/application/solo_spec.rb
+++ b/spec/unit/application/solo_spec.rb
@@ -187,6 +187,31 @@ Enable chef-client interval runs by setting `:client_fork = true` in your config
expect(Chef::Config[:local_mode]).to be_truthy
end
+ context "argv gets tidied up" do
+ before do
+ @original_argv = ARGV.dup
+ ARGV.clear
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ end
+
+ after do
+ ARGV.replace(@original_argv)
+ end
+
+ it "deletes --ez" do
+ ARGV << "--ez"
+ app.reconfigure
+ expect(ARGV.include?("--ez")).to be_falsey
+ end
+
+ it "replaces -r with --recipe-url" do
+ ARGV.push("-r", "http://junglist.gen.nz/recipes.tgz")
+ app.reconfigure
+ expect(ARGV.include?("-r")).to be_falsey
+ expect(ARGV.include?("--recipe-url")).to be_truthy
+ end
+ end
+
it "runs chef-client in local mode" do
allow(app).to receive(:setup_application).and_return(true)
allow(app).to receive(:run_application).and_return(true)
diff --git a/spec/unit/config_fetcher_spec.rb b/spec/unit/config_fetcher_spec.rb
index 35cf27f2af..6847ee5fd3 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, 2)
+ with(invalid_json_error_regex, Chef::Exceptions::DeprecatedExitCode.new)
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, 2)
+ with(invalid_json_error_regex, Chef::Exceptions::DeprecatedExitCode.new)
fetcher.fetch_json
end
end
diff --git a/spec/unit/data_collector/messages/helpers_spec.rb b/spec/unit/data_collector/messages/helpers_spec.rb
new file mode 100644
index 0000000000..0ed0f6c921
--- /dev/null
+++ b/spec/unit/data_collector/messages/helpers_spec.rb
@@ -0,0 +1,190 @@
+#
+# Author:: Adam Leff (<adamleff@chef.io)
+#
+# Copyright:: Copyright 2012-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"
+require "chef/data_collector/messages/helpers"
+
+class TestMessage
+ extend Chef::DataCollector::Messages::Helpers
+end
+
+describe Chef::DataCollector::Messages::Helpers do
+ describe '#organization' do
+ context "when the run is a solo run" do
+ it "returns the data collector organization" do
+ allow(TestMessage).to receive(:solo_run?).and_return(true)
+ expect(TestMessage).to receive(:data_collector_organization).and_return("org1")
+ expect(TestMessage.organization).to eq("org1")
+ end
+ end
+
+ context "when the run is not a solo run" do
+ it "returns the data collector organization" do
+ allow(TestMessage).to receive(:solo_run?).and_return(false)
+ expect(TestMessage).to receive(:chef_server_organization).and_return("org2")
+ expect(TestMessage.organization).to eq("org2")
+ end
+ end
+ end
+
+ describe '#data_collector_organization' do
+ context "when the org is specified in the config" do
+ it "returns the org from the config" do
+ Chef::Config[:data_collector][:organization] = "org1"
+ expect(TestMessage.data_collector_organization).to eq("org1")
+ end
+ end
+
+ context "when the org is not specified in the config" do
+ it "returns the default chef_solo org" do
+ expect(TestMessage.data_collector_organization).to eq("chef_solo")
+ end
+ end
+ end
+
+ describe '#chef_server_organization' do
+ context "when the URL is properly formatted" do
+ it "returns the org from the parsed URL" do
+ Chef::Config[:chef_server_url] = "http://mycompany.com/organizations/myorg"
+ expect(TestMessage.chef_server_organization).to eq("myorg")
+ end
+ end
+
+ context "when the URL is not properly formatted" do
+ it "returns unknown_organization" do
+ Chef::Config[:chef_server_url] = "http://mycompany.com/what/url/is/this"
+ expect(TestMessage.chef_server_organization).to eq("unknown_organization")
+ end
+ end
+ end
+
+ describe '#collector_source' do
+ context "when the run is a solo run" do
+ it "returns chef_solo" do
+ allow(TestMessage).to receive(:solo_run?).and_return(true)
+ expect(TestMessage.collector_source).to eq("chef_solo")
+ end
+ end
+
+ context "when the run is not a solo run" do
+ it "returns chef_client" do
+ allow(TestMessage).to receive(:solo_run?).and_return(false)
+ expect(TestMessage.collector_source).to eq("chef_client")
+ end
+ end
+ end
+
+ describe '#solo_run?' do
+ context "when :solo is set in Chef::Config" do
+ it "returns true" do
+ Chef::Config[:solo] = true
+ Chef::Config[:local_mode] = nil
+ expect(TestMessage.solo_run?).to be_truthy
+ end
+ end
+
+ context "when :local_mode is set in Chef::Config" do
+ it "returns true" do
+ Chef::Config[:solo] = nil
+ Chef::Config[:local_mode] = true
+ expect(TestMessage.solo_run?).to be_truthy
+ end
+ end
+
+ context "when neither :solo or :local_mode is set in Chef::Config" do
+ it "returns false" do
+ Chef::Config[:solo] = nil
+ Chef::Config[:local_mode] = nil
+ expect(TestMessage.solo_run?).to be_falsey
+ end
+ end
+ end
+
+ describe '#node_uuid' do
+ context "when the node UUID can be read" do
+ it "returns the read-in node UUID" do
+ allow(TestMessage).to receive(:read_node_uuid).and_return("read_uuid")
+ expect(TestMessage.node_uuid).to eq("read_uuid")
+ end
+ end
+
+ context "when the node UUID cannot be read" do
+ it "generated a new node UUID" do
+ allow(TestMessage).to receive(:read_node_uuid).and_return(nil)
+ allow(TestMessage).to receive(:generate_node_uuid).and_return("generated_uuid")
+ expect(TestMessage.node_uuid).to eq("generated_uuid")
+ end
+ end
+ end
+
+ describe '#generate_node_uuid' do
+ it "generates a new UUID, stores it, and returns it" do
+ expect(SecureRandom).to receive(:uuid).and_return("generated_uuid")
+ expect(TestMessage).to receive(:update_metadata).with("node_uuid", "generated_uuid")
+ expect(TestMessage.generate_node_uuid).to eq("generated_uuid")
+ end
+ end
+
+ describe '#read_node_uuid' do
+ it "reads the node UUID from metadata" do
+ expect(TestMessage).to receive(:metadata).and_return({ "node_uuid" => "read_uuid" })
+ expect(TestMessage.read_node_uuid).to eq("read_uuid")
+ end
+ end
+
+ describe "metadata" do
+ let(:metadata_filename) { "fake_metadata_file.json" }
+
+ before do
+ allow(TestMessage).to receive(:metadata_filename).and_return(metadata_filename)
+ end
+
+ context "when the metadata file exists" do
+ it "returns the contents of the metadata file" do
+ expect(Chef::FileCache).to receive(:load).with(metadata_filename).and_return('{"foo":"bar"}')
+ expect(TestMessage.metadata["foo"]).to eq("bar")
+ end
+ end
+
+ context "when the metadata file does not exist" do
+ it "returns an empty hash" do
+ expect(Chef::FileCache).to receive(:load).with(metadata_filename).and_raise(Chef::Exceptions::FileNotFound)
+ expect(TestMessage.metadata).to eq({})
+ end
+ end
+ end
+
+ describe '#update_metadata' do
+ let(:metadata) { double("metadata") }
+
+ it "updates the file" do
+ allow(TestMessage).to receive(:metadata_filename).and_return("fake_metadata_file.json")
+ allow(TestMessage).to receive(:metadata).and_return(metadata)
+ expect(metadata).to receive(:[]=).with("new_key", "new_value")
+ expect(metadata).to receive(:to_json).and_return("metadata_json")
+ expect(Chef::FileCache).to receive(:store).with(
+ "fake_metadata_file.json",
+ "metadata_json",
+ 0644
+ )
+
+ TestMessage.update_metadata("new_key", "new_value")
+ end
+ end
+end
diff --git a/spec/unit/data_collector/messages_spec.rb b/spec/unit/data_collector/messages_spec.rb
new file mode 100644
index 0000000000..dee86a3907
--- /dev/null
+++ b/spec/unit/data_collector/messages_spec.rb
@@ -0,0 +1,214 @@
+#
+# Author:: Adam Leff (<adamleff@chef.io)
+#
+# Copyright:: Copyright 2012-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"
+require "chef/data_collector/messages/helpers"
+
+describe Chef::DataCollector::Messages do
+ describe '#run_start_message' do
+ let(:run_status) { Chef::RunStatus.new(Chef::Node.new, Chef::EventDispatch::Dispatcher.new) }
+ let(:required_fields) do
+ %w{
+ chef_server_fqdn
+ entity_uuid
+ id
+ message_version
+ message_type
+ node_name
+ organization_name
+ run_id
+ source
+ start_time
+ }
+ end
+ let(:optional_fields) { [] }
+
+ before do
+ allow(run_status).to receive(:start_time).and_return(Time.now)
+ end
+
+ it "is not missing any required fields" do
+ missing_fields = required_fields.select do |key|
+ !Chef::DataCollector::Messages.run_start_message(run_status).key?(key)
+ end
+
+ expect(missing_fields).to eq([])
+ end
+
+ it "does not have any extra fields" do
+ extra_fields = Chef::DataCollector::Messages.run_start_message(run_status).keys.select do |key|
+ !required_fields.include?(key) && !optional_fields.include?(key)
+ end
+
+ expect(extra_fields).to eq([])
+ end
+ end
+
+ describe '#run_end_message' do
+ let(:run_status) { Chef::RunStatus.new(Chef::Node.new, Chef::EventDispatch::Dispatcher.new) }
+ let(:resource1) { double("resource1", for_json: "resource_data", status: "updated") }
+ let(:resource2) { double("resource2", for_json: "resource_data", status: "skipped") }
+ let(:reporter_data) do
+ {
+ run_status: run_status,
+ completed_resources: [resource1, resource2],
+ }
+ 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)
+ 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_name
+ organization_name
+ resources
+ run_id
+ run_list
+ source
+ start_time
+ status
+ total_resource_count
+ updated_resource_count
+ }
+ end
+ let(:optional_fields) { %w{error} }
+
+ 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_name
+ organization_name
+ resources
+ run_id
+ run_list
+ source
+ start_time
+ status
+ total_resource_count
+ updated_resource_count
+ }
+ end
+ let(:optional_fields) { [] }
+
+ 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 '#node_update_message' do
+ let(:run_status) { Chef::RunStatus.new(Chef::Node.new, Chef::EventDispatch::Dispatcher.new) }
+
+ let(:required_fields) do
+ %w{
+ entity_name
+ entity_type
+ entity_uuid
+ id
+ message_type
+ message_version
+ organization_name
+ recorded_at
+ remote_hostname
+ requestor_name
+ requestor_type
+ run_id
+ service_hostname
+ source
+ task
+ user_agent
+ }
+ end
+ let(:optional_fields) { %w{data} }
+
+ it "is not missing any required fields" do
+ missing_fields = required_fields.select do |key|
+ !Chef::DataCollector::Messages.node_update_message(run_status).key?(key)
+ end
+
+ expect(missing_fields).to eq([])
+ end
+
+ it "does not have any extra fields" do
+ extra_fields = Chef::DataCollector::Messages.node_update_message(run_status).keys.select do |key|
+ !required_fields.include?(key) && !optional_fields.include?(key)
+ end
+
+ expect(extra_fields).to eq([])
+ end
+ end
+end
diff --git a/spec/unit/data_collector_spec.rb b/spec/unit/data_collector_spec.rb
new file mode 100644
index 0000000000..78b0aaf97d
--- /dev/null
+++ b/spec/unit/data_collector_spec.rb
@@ -0,0 +1,502 @@
+#
+# Author:: Adam Leff (<adamleff@chef.io)
+# Author:: Ryan Cragun (<ryan@chef.io>)
+#
+# Copyright:: Copyright 2012-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"
+require "chef/data_collector"
+
+describe Chef::DataCollector do
+ describe ".register_reporter?" do
+ context "when no data collector URL is configured" do
+ it "returns false" do
+ Chef::Config[:data_collector][:server_url] = nil
+ expect(Chef::DataCollector.register_reporter?).to be_falsey
+ end
+ end
+
+ context "when a data collector URL is configured" do
+ before do
+ Chef::Config[:data_collector][:server_url] = "http://data_collector"
+ end
+
+ context "when operating in why_run mode" do
+ it "returns false" do
+ Chef::Config[:why_run] = true
+ expect(Chef::DataCollector.register_reporter?).to be_falsey
+ end
+ end
+
+ context "when not operating in why_run mode" do
+ before do
+ Chef::Config[:why_run] = false
+ end
+
+ context "when report is enabled for current mode" do
+ it "returns true" do
+ allow(Chef::DataCollector).to receive(:reporter_enabled_for_current_mode?).and_return(true)
+ expect(Chef::DataCollector.register_reporter?).to be_truthy
+ end
+ end
+
+ context "when report is disabled for current mode" do
+ it "returns false" do
+ allow(Chef::DataCollector).to receive(:reporter_enabled_for_current_mode?).and_return(false)
+ expect(Chef::DataCollector.register_reporter?).to be_falsey
+ end
+ end
+ end
+ end
+ end
+
+ describe ".reporter_enabled_for_current_mode?" do
+ context "when running in solo mode" do
+ before do
+ Chef::Config[:solo] = true
+ Chef::Config[:local_mode] = false
+ end
+
+ context "when data_collector_mode is :solo" do
+ it "returns true" do
+ Chef::Config[:data_collector][:mode] = :solo
+ expect(Chef::DataCollector.reporter_enabled_for_current_mode?).to eq(true)
+ end
+ end
+
+ context "when data_collector_mode is :client" do
+ it "returns false" do
+ Chef::Config[:data_collector][:mode] = :client
+ expect(Chef::DataCollector.reporter_enabled_for_current_mode?).to eq(false)
+ end
+ end
+
+ context "when data_collector_mode is :both" do
+ it "returns true" do
+ Chef::Config[:data_collector][:mode] = :both
+ expect(Chef::DataCollector.reporter_enabled_for_current_mode?).to eq(true)
+ end
+ end
+ end
+
+ context "when running in local mode" do
+ before do
+ Chef::Config[:solo] = false
+ Chef::Config[:local_mode] = true
+ end
+
+ context "when data_collector_mode is :solo" do
+ it "returns true" do
+ Chef::Config[:data_collector][:mode] = :solo
+ expect(Chef::DataCollector.reporter_enabled_for_current_mode?).to eq(true)
+ end
+ end
+
+ context "when data_collector_mode is :client" do
+ it "returns false" do
+ Chef::Config[:data_collector][:mode] = :client
+ expect(Chef::DataCollector.reporter_enabled_for_current_mode?).to eq(false)
+ end
+ end
+
+ context "when data_collector_mode is :both" do
+ it "returns true" do
+ Chef::Config[:data_collector][:mode] = :both
+ expect(Chef::DataCollector.reporter_enabled_for_current_mode?).to eq(true)
+ end
+ end
+ end
+
+ context "when running in client mode" do
+ before do
+ Chef::Config[:solo] = false
+ Chef::Config[:local_mode] = false
+ end
+
+ context "when data_collector_mode is :solo" do
+ it "returns false" do
+ Chef::Config[:data_collector][:mode] = :solo
+ expect(Chef::DataCollector.reporter_enabled_for_current_mode?).to eq(false)
+ end
+ end
+
+ context "when data_collector_mode is :client" do
+ it "returns true" do
+ Chef::Config[:data_collector][:mode] = :client
+ expect(Chef::DataCollector.reporter_enabled_for_current_mode?).to eq(true)
+ end
+ end
+
+ context "when data_collector_mode is :both" do
+ it "returns true" do
+ Chef::Config[:data_collector][:mode] = :both
+ expect(Chef::DataCollector.reporter_enabled_for_current_mode?).to eq(true)
+ end
+ end
+ end
+ end
+end
+
+describe Chef::DataCollector::Reporter do
+ let(:reporter) { described_class.new }
+ let(:run_status) { Chef::RunStatus.new(Chef::Node.new, Chef::EventDispatch::Dispatcher.new) }
+
+ describe '#run_started' do
+ before do
+ allow(reporter).to receive(:update_run_status)
+ allow(reporter).to receive(:send_to_data_collector)
+ allow(Chef::DataCollector::Messages).to receive(:run_start_message)
+ end
+
+ it "updates the run status" do
+ expect(reporter).to receive(:update_run_status).with(run_status)
+ reporter.run_started(run_status)
+ end
+
+ it "sends the RunStart message output to the Data Collector server" do
+ expect(Chef::DataCollector::Messages)
+ .to receive(:run_start_message)
+ .with(run_status)
+ .and_return(key: "value")
+ expect(reporter).to receive(:send_to_data_collector).with('{"key":"value"}')
+ reporter.run_started(run_status)
+ end
+ end
+
+ describe '#run_completed' do
+ it "sends the run completion" do
+ node = Chef::Node.new
+
+ expect(reporter).to receive(:send_run_completion).with(status: "success")
+ reporter.run_completed(node)
+ end
+ end
+
+ describe '#run_failed' do
+ it "updates the exception and sends the run completion" do
+ expect(reporter).to receive(:send_run_completion).with(status: "failure")
+ reporter.run_failed("test_exception")
+ end
+ end
+
+ describe '#resource_current_state_loaded' do
+ let(:new_resource) { double("new_resource") }
+ let(:action) { double("action") }
+ let(:current_resource) { double("current_resource") }
+
+ context "when resource is a nested resource" do
+ it "does not update the resource report" do
+ allow(reporter).to receive(:nested_resource?).and_return(true)
+ expect(reporter).not_to receive(:update_current_resource_report)
+ reporter.resource_current_state_loaded(new_resource, action, current_resource)
+ end
+ end
+
+ context "when resource is not a nested resource" do
+ it "updates the resource report" do
+ allow(reporter).to receive(:nested_resource?).and_return(false)
+ expect(Chef::DataCollector::ResourceReport).to receive(:new).with(
+ new_resource,
+ action,
+ current_resource)
+ .and_return("resource_report")
+ expect(reporter).to receive(:update_current_resource_report).with("resource_report")
+ reporter.resource_current_state_loaded(new_resource, action, current_resource)
+ end
+ end
+ end
+
+ describe '#resource_up_to_date' do
+ let(:new_resource) { double("new_resource") }
+ let(:action) { double("action") }
+ let(:resource_report) { double("resource_report") }
+
+ before do
+ allow(reporter).to receive(:nested_resource?)
+ allow(reporter).to receive(:current_resource_report).and_return(resource_report)
+ allow(resource_report).to receive(:up_to_date)
+ end
+
+ context "when the resource is a nested resource" do
+ it "does not mark the resource report as up-to-date" do
+ allow(reporter).to receive(:nested_resource?).with(new_resource).and_return(true)
+ expect(resource_report).not_to receive(:up_to_date)
+ reporter.resource_up_to_date(new_resource, action)
+ end
+ end
+
+ context "when the resource is not a nested resource" do
+ it "marks the resource report as up-to-date" do
+ allow(reporter).to receive(:nested_resource?).with(new_resource).and_return(false)
+ expect(resource_report).to receive(:up_to_date)
+ reporter.resource_up_to_date(new_resource, action)
+ end
+ end
+ end
+
+ describe '#resource_skipped' do
+ let(:new_resource) { double("new_resource") }
+ let(:action) { double("action") }
+ let(:conditional) { double("conditional") }
+ let(:resource_report) { double("resource_report") }
+
+ before do
+ allow(reporter).to receive(:nested_resource?)
+ allow(reporter).to receive(:current_resource_report).and_return(resource_report)
+ allow(resource_report).to receive(:skipped)
+ end
+
+ context "when the resource is a nested resource" do
+ it "does not mark the resource report as skipped" do
+ allow(reporter).to receive(:nested_resource?).with(new_resource).and_return(true)
+ expect(resource_report).not_to receive(:skipped).with(conditional)
+ reporter.resource_skipped(new_resource, action, conditional)
+ end
+ end
+
+ context "when the resource is not a nested resource" do
+ it "updates the resource report" do
+ allow(reporter).to receive(:nested_resource?).and_return(false)
+ expect(Chef::DataCollector::ResourceReport).to receive(:new).with(
+ new_resource,
+ action)
+ .and_return("resource_report")
+ expect(reporter).to receive(:update_current_resource_report).with("resource_report")
+ reporter.resource_skipped(new_resource, action, conditional)
+ end
+
+ it "marks the resource report as skipped" do
+ allow(reporter).to receive(:nested_resource?).with(new_resource).and_return(false)
+ expect(resource_report).to receive(:skipped).with(conditional)
+ reporter.resource_skipped(new_resource, action, conditional)
+ end
+ end
+ end
+
+ describe '#resource_updated' do
+ let(:resource_report) { double("resource_report") }
+
+ before do
+ allow(reporter).to receive(:current_resource_report).and_return(resource_report)
+ allow(resource_report).to receive(:updated)
+ end
+
+ it "marks the resource report as updated" do
+ expect(resource_report).to receive(:updated)
+ reporter.resource_updated("new_resource", "action")
+ end
+ end
+
+ describe '#resource_failed' do
+ let(:new_resource) { double("new_resource") }
+ let(:action) { double("action") }
+ let(:exception) { double("exception") }
+ let(:error_mapper) { double("error_mapper") }
+ let(:resource_report) { double("resource_report") }
+
+ before do
+ allow(reporter).to receive(:update_error_description)
+ allow(reporter).to receive(:current_resource_report).and_return(resource_report)
+ allow(resource_report).to receive(:failed)
+ allow(Chef::Formatters::ErrorMapper).to receive(:resource_failed).and_return(error_mapper)
+ allow(error_mapper).to receive(:for_json)
+ end
+
+ it "updates the error description" do
+ expect(Chef::Formatters::ErrorMapper).to receive(:resource_failed).with(
+ new_resource,
+ action,
+ exception
+ ).and_return(error_mapper)
+ expect(error_mapper).to receive(:for_json).and_return("error_description")
+ expect(reporter).to receive(:update_error_description).with("error_description")
+ reporter.resource_failed(new_resource, action, exception)
+ end
+
+ context "when the resource is not a nested resource" do
+ it "marks the resource report as failed" do
+ allow(reporter).to receive(:nested_resource?).with(new_resource).and_return(false)
+ expect(resource_report).to receive(:failed).with(exception)
+ reporter.resource_failed(new_resource, action, exception)
+ end
+ end
+
+ context "when the resource is a nested resource" do
+ it "does not mark the resource report as failed" do
+ allow(reporter).to receive(:nested_resource?).with(new_resource).and_return(true)
+ expect(resource_report).not_to receive(:failed).with(exception)
+ reporter.resource_failed(new_resource, action, exception)
+ end
+ end
+ end
+
+ describe '#resource_completed' do
+ let(:new_resource) { double("new_resource") }
+ let(:resource_report) { double("resource_report") }
+
+ before do
+ allow(reporter).to receive(:add_completed_resource)
+ allow(reporter).to receive(:update_current_resource_report)
+ allow(resource_report).to receive(:finish)
+ end
+
+ context "when there is no current resource report" do
+ it "does not add the updated resource" do
+ allow(reporter).to receive(:current_resource_report).and_return(nil)
+ expect(reporter).not_to receive(:add_completed_resource)
+ reporter.resource_completed(new_resource)
+ end
+ end
+
+ context "when there is a current resource report" do
+ before do
+ allow(reporter).to receive(:current_resource_report).and_return(resource_report)
+ end
+
+ context "when the resource is a nested resource" do
+ it "does not add the updated resource" do
+ allow(reporter).to receive(:nested_resource?).with(new_resource).and_return(true)
+ expect(reporter).not_to receive(:add_completed_resource)
+ reporter.resource_completed(new_resource)
+ end
+ end
+
+ context "when the resource is not a nested resource" do
+ before do
+ allow(reporter).to receive(:nested_resource?).with(new_resource).and_return(false)
+ end
+
+ it "marks the current resource report as finished" do
+ expect(resource_report).to receive(:finish)
+ reporter.resource_completed(new_resource)
+ end
+
+ it "adds the resource to the updated resource list" do
+ expect(reporter).to receive(:add_completed_resource).with(resource_report)
+ reporter.resource_completed(new_resource)
+ end
+
+ it "nils out the current resource report" do
+ expect(reporter).to receive(:update_current_resource_report).with(nil)
+ reporter.resource_completed(new_resource)
+ end
+ end
+ end
+ end
+
+ describe '#run_list_expanded' do
+ it "sets the expanded run list" do
+ reporter.run_list_expanded("test_run_list")
+ expect(reporter.expanded_run_list).to eq("test_run_list")
+ end
+ end
+
+ describe '#run_list_expand_failed' do
+ let(:node) { double("node") }
+ let(:error_mapper) { double("error_mapper") }
+ let(:exception) { double("exception") }
+
+ it "updates the error description" do
+ expect(Chef::Formatters::ErrorMapper).to receive(:run_list_expand_failed).with(
+ node,
+ exception
+ ).and_return(error_mapper)
+ expect(error_mapper).to receive(:for_json).and_return("error_description")
+ expect(reporter).to receive(:update_error_description).with("error_description")
+ reporter.run_list_expand_failed(node, exception)
+ end
+ end
+
+ describe '#cookbook_resolution_failed' do
+ let(:error_mapper) { double("error_mapper") }
+ let(:exception) { double("exception") }
+ let(:expanded_run_list) { double("expanded_run_list") }
+
+ it "updates the error description" do
+ expect(Chef::Formatters::ErrorMapper).to receive(:cookbook_resolution_failed).with(
+ expanded_run_list,
+ exception
+ ).and_return(error_mapper)
+ expect(error_mapper).to receive(:for_json).and_return("error_description")
+ expect(reporter).to receive(:update_error_description).with("error_description")
+ reporter.cookbook_resolution_failed(expanded_run_list, exception)
+ end
+
+ end
+
+ describe '#cookbook_sync_failed' do
+ let(:cookbooks) { double("cookbooks") }
+ let(:error_mapper) { double("error_mapper") }
+ let(:exception) { double("exception") }
+
+ it "updates the error description" do
+ expect(Chef::Formatters::ErrorMapper).to receive(:cookbook_sync_failed).with(
+ cookbooks,
+ exception
+ ).and_return(error_mapper)
+ expect(error_mapper).to receive(:for_json).and_return("error_description")
+ expect(reporter).to receive(:update_error_description).with("error_description")
+ reporter.cookbook_sync_failed(cookbooks, exception)
+ end
+ end
+
+ describe '#disable_reporter_on_error' do
+ context "when no exception is raise by the block" do
+ it "does not disable the reporter" do
+ expect(reporter).not_to receive(:disable_data_collector_reporter)
+ reporter.send(:disable_reporter_on_error) { true }
+ end
+
+ it "does not raise an exception" do
+ expect { reporter.send(:disable_reporter_on_error) { true } }.not_to raise_error
+ end
+ end
+
+ context "when an unexpected exception is raised by the block" do
+ it "re-raises the exception" do
+ expect { reporter.send(:disable_reporter_on_error) { raise RuntimeError, "bummer" } }.to raise_error(RuntimeError)
+ end
+ end
+
+ [ Timeout::Error, Errno::EINVAL, Errno::ECONNRESET,
+ Errno::ECONNREFUSED, EOFError, Net::HTTPBadResponse,
+ Net::HTTPHeaderSyntaxError, Net::ProtocolError, OpenSSL::SSL::SSLError ].each do |exception_class|
+ context "when the block raises a #{exception_class} exception" do
+ it "disables the reporter" do
+ expect(reporter).to receive(:disable_data_collector_reporter)
+ reporter.send(:disable_reporter_on_error) { raise exception_class.new("bummer") }
+ end
+
+ context "when raise-on-failure is enabled" do
+ it "logs an error and raises" do
+ Chef::Config[:data_collector][:raise_on_failure] = true
+ expect(Chef::Log).to receive(:error)
+ expect { reporter.send(:disable_reporter_on_error) { raise exception_class.new("bummer") } }.to raise_error(exception_class)
+ end
+ end
+
+ context "when raise-on-failure is disabled" do
+ it "logs a warning and does not raise an exception" do
+ Chef::Config[:data_collector][:raise_on_failure] = false
+ expect(Chef::Log).to receive(:warn)
+ expect { reporter.send(:disable_reporter_on_error) { raise exception_class.new("bummer") } }.not_to raise_error
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/unit/provider/directory_spec.rb b/spec/unit/provider/directory_spec.rb
index f8864af7f8..aebbaa6e81 100644
--- a/spec/unit/provider/directory_spec.rb
+++ b/spec/unit/provider/directory_spec.rb
@@ -227,7 +227,7 @@ describe Chef::Provider::Directory do
end
end
- describe "#run_action(:create)" do
+ describe "#run_action(:delete)" do
describe "when the directory exists" do
it "deletes the directory" do
directory.run_action(:delete)
@@ -238,6 +238,16 @@ describe Chef::Provider::Directory do
directory.run_action(:delete)
expect(new_resource).to be_updated
end
+
+ it "does not use rm_rf which silently consumes errors" do
+ expect(FileUtils).not_to receive(:rm_rf)
+ expect(FileUtils).to receive(:rm_r)
+ # set recursive or FileUtils isn't used at all.
+ new_resource.recursive(true)
+ directory.run_action(:delete)
+ # reset back...
+ new_resource.recursive(false)
+ end
end
describe "when the directory does not exist" do
diff --git a/spec/unit/provider/package/chocolatey_spec.rb b/spec/unit/provider/package/chocolatey_spec.rb
index 8eaa69b598..8344c3d0ec 100644
--- a/spec/unit/provider/package/chocolatey_spec.rb
+++ b/spec/unit/provider/package/chocolatey_spec.rb
@@ -59,7 +59,7 @@ Git|2.6.2
munin-node|1.6.1.20130823
EOF
remote_list_obj = double(stdout: remote_list_stdout)
- allow(provider).to receive(:shell_out!).with("#{choco_exe} list -ar #{package_names.join ' '}#{args}", { timeout: timeout }).and_return(remote_list_obj)
+ allow(provider).to receive(:shell_out!).with("#{choco_exe} list -r #{package_names.join ' '}#{args}", { timeout: timeout }).and_return(remote_list_obj)
end
describe "#initialize" do
@@ -84,12 +84,6 @@ munin-node|1.6.1.20130823
expect(provider.candidate_version).to eql(["2.6.1"])
end
- it "should set the candidate_version to nill if pinning to bogus version" do
- allow_remote_list(["git"])
- new_resource.version("2.5.0")
- expect(provider.candidate_version).to eql([nil])
- end
-
it "should set the candidate_version to nil if there is no candidate" do
allow_remote_list(["vim"])
new_resource.package_name("vim")
@@ -301,14 +295,6 @@ munin-node|1.6.1.20130823
expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package)
end
- it "installing a package version that does not exist throws an error" do
- allow_remote_list(["git"])
- new_resource.package_name("git")
- new_resource.version("2.7.0")
- provider.load_current_resource
- expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package)
- end
-
it "installing multiple packages with a package that does not exist throws an error" do
allow_remote_list(["git", "package-does-not-exist"])
new_resource.package_name(["git", "package-does-not-exist"])
diff --git a/spec/unit/provider/package/portage_spec.rb b/spec/unit/provider/package/portage_spec.rb
index c2ff1cc952..ebb5b3139f 100644
--- a/spec/unit/provider/package/portage_spec.rb
+++ b/spec/unit/provider/package/portage_spec.rb
@@ -56,6 +56,12 @@ describe Chef::Provider::Package::Portage, "load_current_resource" do
expect(@provider.current_resource.version).to eq("1.0.0-r1")
end
+ it "should return a current resource with the correct version if the package is found with version with character" do
+ allow(::Dir).to receive(:[]).with("/var/db/pkg/dev-util/git-*").and_return(["/var/db/pkg/dev-util/git-1.0.0d"])
+ @provider.load_current_resource
+ expect(@provider.current_resource.version).to eq("1.0.0d")
+ end
+
it "should return a current resource with a nil version if the package is not found" do
allow(::Dir).to receive(:[]).with("/var/db/pkg/dev-util/git-*").and_return(["/var/db/pkg/dev-util/notgit-1.0.0"])
@provider.load_current_resource
diff --git a/spec/unit/provider/package/yum/yum_cache_spec.rb b/spec/unit/provider/package/yum/yum_cache_spec.rb
new file mode 100644
index 0000000000..e9d615d734
--- /dev/null
+++ b/spec/unit/provider/package/yum/yum_cache_spec.rb
@@ -0,0 +1,27 @@
+#
+# 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 "spec_helper"
+
+describe Chef::Provider::Package::Yum::YumCache do
+
+ it "can find yum-dump.py" do
+ expect(File.exist?(Chef::Provider::Package::Yum::YumCache.instance.yum_dump_path)).to be true
+ end
+
+end
diff --git a/spec/unit/provider/systemd_unit_spec.rb b/spec/unit/provider/systemd_unit_spec.rb
index 0babc30808..42604c22eb 100644
--- a/spec/unit/provider/systemd_unit_spec.rb
+++ b/spec/unit/provider/systemd_unit_spec.rb
@@ -654,6 +654,69 @@ describe Chef::Provider::SystemdUnit do
end
end
+ describe "try-restarts the unit" do
+ context "when a user is specified" do
+ it "try-restarts the unit" do
+ current_resource.user(user_name)
+ expect(provider).to receive(:shell_out_with_systems_locale!)
+ .with("#{systemctl_path} --user try-restart #{unit_name}", user_cmd_opts)
+ .and_return(shell_out_success)
+ provider.action_try_restart
+ end
+ end
+
+ context "when no user is specified" do
+ it "try-restarts the unit" do
+ expect(provider).to receive(:shell_out_with_systems_locale!)
+ .with("#{systemctl_path} --system try-restart #{unit_name}", {})
+ .and_return(shell_out_success)
+ provider.action_try_restart
+ end
+ end
+ end
+
+ describe "reload-or-restarts the unit" do
+ context "when a user is specified" do
+ it "reload-or-restarts the unit" do
+ current_resource.user(user_name)
+ expect(provider).to receive(:shell_out_with_systems_locale!)
+ .with("#{systemctl_path} --user reload-or-restart #{unit_name}", user_cmd_opts)
+ .and_return(shell_out_success)
+ provider.action_reload_or_restart
+ end
+ end
+
+ context "when no user is specified" do
+ it "reload-or-restarts the unit" do
+ expect(provider).to receive(:shell_out_with_systems_locale!)
+ .with("#{systemctl_path} --system reload-or-restart #{unit_name}", {})
+ .and_return(shell_out_success)
+ provider.action_reload_or_restart
+ end
+ end
+ end
+
+ describe "reload-or-try-restarts the unit" do
+ context "when a user is specified" do
+ it "reload-or-try-restarts the unit" do
+ current_resource.user(user_name)
+ expect(provider).to receive(:shell_out_with_systems_locale!)
+ .with("#{systemctl_path} --user reload-or-try-restart #{unit_name}", user_cmd_opts)
+ .and_return(shell_out_success)
+ provider.action_reload_or_try_restart
+ end
+ end
+
+ context "when no user is specified" do
+ it "reload-or-try-restarts the unit" do
+ expect(provider).to receive(:shell_out_with_systems_locale!)
+ .with("#{systemctl_path} --system reload-or-try-restart #{unit_name}", {})
+ .and_return(shell_out_success)
+ provider.action_reload_or_try_restart
+ end
+ end
+ end
+
describe "#active?" do
before(:each) do
provider.current_resource = current_resource
diff --git a/tasks/bin/bundle-platform b/tasks/bin/bundle-platform
index 7c77393cb1..4bd659d307 100755
--- a/tasks/bin/bundle-platform
+++ b/tasks/bin/bundle-platform
@@ -1,10 +1,14 @@
#!/usr/bin/env ruby
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.split(" ").map { |p| Gem::Platform.new(p) }
+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",
@@ -12,4 +16,4 @@ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile",
require "rubygems"
-load Gem.bin_path("bundler", "bundle")
+load Gem.bin_path("bundler", "bundle", desired_version)
diff --git a/tasks/bundle_util.rb b/tasks/bundle_util.rb
index a057db858a..67647dd4f0 100644
--- a/tasks/bundle_util.rb
+++ b/tasks/bundle_util.rb
@@ -57,7 +57,14 @@ module BundleUtil
# Run the bundle command
ruby_platforms = platform ? PLATFORMS[platform].join(" ") : "ruby"
- cmd = Shellwords.join([Gem.ruby, "-S", bundle_platform, ruby_platforms, *args])
+ 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}"
@@ -65,7 +72,7 @@ module BundleUtil
if extract_output
`#{cmd}`
else
- unless system(bundle_platform, ruby_platforms, *args)
+ unless system(bundle_platform, ruby_platforms, "_#{desired_bundler_version}_", *args)
raise "#{bundle_platform} failed: exit code #{$?}"
end
end
@@ -91,4 +98,13 @@ module BundleUtil
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/vendor/bundle/bundler/gems/bundler-audit-4e32fca89d75 b/vendor/bundle/bundler/gems/bundler-audit-4e32fca89d75
new file mode 160000
+Subproject 4e32fca89d75f0e249671431ff38aadc02bfb28
diff --git a/vendor/bundle/bundler/gems/chefstyle-52a0d55a9e8f b/vendor/bundle/bundler/gems/chefstyle-52a0d55a9e8f
new file mode 160000
+Subproject 52a0d55a9e8fb30d067c0a66371db4ae781a026