diff options
106 files changed, 4892 insertions, 1463 deletions
@@ -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. @@ -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" @@ -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 @@ -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 |